diff --git a/go.mod b/go.mod index fb5b3c527..d95f34457 100644 --- a/go.mod +++ b/go.mod @@ -60,7 +60,7 @@ require ( github.com/oklog/run v1.2.0 github.com/olekukonko/tablewriter v1.1.0 github.com/onsi/ginkgo v1.16.5 - github.com/onsi/ginkgo/v2 v2.25.3 + github.com/onsi/ginkgo/v2 v2.26.0 github.com/onsi/gomega v1.38.2 github.com/open-policy-agent/opa v1.9.0 github.com/opencloud-eu/icap-client v0.0.0-20250930132611-28a2afe62d89 @@ -228,7 +228,7 @@ require ( github.com/gobwas/pool v0.2.1 // indirect github.com/gobwas/ws v1.2.1 // indirect github.com/goccy/go-json v0.10.5 // indirect - github.com/goccy/go-yaml v1.12.0 // indirect + github.com/goccy/go-yaml v1.18.0 // indirect github.com/gofrs/flock v0.12.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.2 // indirect @@ -378,7 +378,6 @@ require ( golang.org/x/sys v0.36.0 // indirect golang.org/x/time v0.13.0 // indirect golang.org/x/tools v0.36.0 // indirect - golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect diff --git a/go.sum b/go.sum index 80aaf48a8..ea0715e1a 100644 --- a/go.sum +++ b/go.sum @@ -357,6 +357,12 @@ github.com/getkin/kin-openapi v0.13.0/go.mod h1:WGRs2ZMM1Q8LR1QBEwUxC6RJEfaBcD0s github.com/ggwhite/go-masker v1.1.0 h1:kN/KIvktu2U+hd3KWrSlLj7xBGD1iBfc9/xdbVgFbRc= github.com/ggwhite/go-masker v1.1.0/go.mod h1:xnTRHwrIU9FtBADwEjUC5Dy/BVedvoTxyOE7/d3CNwY= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BNhXs= +github.com/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo= +github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M= +github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk= +github.com/gkampitakis/go-snaps v0.5.14 h1:3fAqdB6BCPKHDMHAKRwtPUwYexKtGrNuw8HX/T/4neo= +github.com/gkampitakis/go-snaps v0.5.14/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc= github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= github.com/go-acme/lego/v4 v4.4.0 h1:uHhU5LpOYQOdp3aDU+XY2bajseu8fuExphTL1Ss6/Fc= @@ -471,8 +477,8 @@ github.com/gobwas/ws v1.2.1 h1:F2aeBZrm2NDsc7vbovKrWSogd4wvfAxg0FQ89/iqOTk= github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= -github.com/goccy/go-yaml v1.12.0 h1:/1WHjnMsI1dlIBQutrvSMGZRQufVO3asrHfTwfACoPM= -github.com/goccy/go-yaml v1.12.0/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU= +github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= +github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= @@ -687,6 +693,8 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfC github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I= github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60= +github.com/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE= +github.com/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= @@ -780,6 +788,8 @@ github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czP github.com/magiconair/properties v1.8.4/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo= +github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg= github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= github.com/mattermost/xml-roundtrip-validator v0.1.0 h1:RXbVD2UAl7A7nOTR4u7E3ILa4IbtvKBHw64LDsmu9hU= github.com/mattermost/xml-roundtrip-validator v0.1.0/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To= @@ -817,6 +827,8 @@ github.com/maxymania/go-system v0.0.0-20170110133659-647cc364bf0b h1:Q53idHrTuQD github.com/maxymania/go-system v0.0.0-20170110133659-647cc364bf0b/go.mod h1:KirJrATYGbTyUwVR26xIkaipRqRcMRXBf8N5dacvGus= github.com/mendsley/gojwk v0.0.0-20141217222730-4d5ec6e58103 h1:Z/i1e+gTZrmcGeZyWckaLfucYG6KYOXLWo4co8pZYNY= github.com/mendsley/gojwk v0.0.0-20141217222730-4d5ec6e58103/go.mod h1:o9YPB5aGP8ob35Vy6+vyq3P3bWe7NQWzf+JLiXCiMaE= +github.com/mfridman/tparse v0.18.0 h1:wh6dzOKaIwkUGyKgOntDW4liXSo37qg5AXbIhkMV3vE= +github.com/mfridman/tparse v0.18.0/go.mod h1:gEvqZTuCgEhPbYk/2lS3Kcxg1GmTxxU7kTC8DvP0i/A= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.40/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM= @@ -919,8 +931,8 @@ github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/ginkgo/v2 v2.25.3 h1:Ty8+Yi/ayDAGtk4XxmmfUy4GabvM+MegeB4cDLRi6nw= -github.com/onsi/ginkgo/v2 v2.25.3/go.mod h1:43uiyQC4Ed2tkOzLsEYm7hnrb7UJTWHYNsuy3bG/snE= +github.com/onsi/ginkgo/v2 v2.26.0 h1:1J4Wut1IlYZNEAWIV3ALrT9NfiaGW2cDCJQSFQMs/gE= +github.com/onsi/ginkgo/v2 v2.26.0/go.mod h1:qhEywmzWTBUY88kfO0BRvX4py7scov9yR+Az2oavUzw= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= @@ -1641,8 +1653,6 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= diff --git a/vendor/github.com/goccy/go-yaml/.gitignore b/vendor/github.com/goccy/go-yaml/.gitignore new file mode 100644 index 000000000..47952bd9a --- /dev/null +++ b/vendor/github.com/goccy/go-yaml/.gitignore @@ -0,0 +1,3 @@ +bin/ +.idea/ +cover.out diff --git a/vendor/github.com/goccy/go-yaml/.golangci.yml b/vendor/github.com/goccy/go-yaml/.golangci.yml new file mode 100644 index 000000000..05675c895 --- /dev/null +++ b/vendor/github.com/goccy/go-yaml/.golangci.yml @@ -0,0 +1,65 @@ +version: "2" +linters: + default: none + enable: + - errcheck + - govet + - ineffassign + - misspell + - perfsprint + - staticcheck + - unused + settings: + errcheck: + without_tests: true + govet: + disable: + - tests + misspell: + locale: US + perfsprint: + int-conversion: false + err-error: false + errorf: true + sprintf1: false + strconcat: false + staticcheck: + checks: + - -ST1000 + - -ST1005 + - all + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + rules: + - linters: + - staticcheck + path: _test\.go + paths: + - third_party$ + - builtin$ + - examples$ +formatters: + enable: + - gci + - gofmt + settings: + gci: + sections: + - standard + - default + - prefix(github.com/goccy/go-yaml) + - blank + - dot + gofmt: + simplify: true + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ diff --git a/vendor/github.com/goccy/go-yaml/Makefile b/vendor/github.com/goccy/go-yaml/Makefile index 1b1d92397..599361646 100644 --- a/vendor/github.com/goccy/go-yaml/Makefile +++ b/vendor/github.com/goccy/go-yaml/Makefile @@ -1,19 +1,55 @@ +## Location to install dependencies to +LOCALBIN ?= $(shell pwd)/bin +TESTMOD := testdata/go_test.mod + +$(LOCALBIN): + mkdir -p $(LOCALBIN) + .PHONY: test test: go test -v -race ./... + go test -v -race ./testdata -modfile=$(TESTMOD) .PHONY: simple-test simple-test: go test -v ./... + go test -v ./testdata -modfile=$(TESTMOD) + +.PHONY: fuzz +fuzz: + go test -fuzz=Fuzz -fuzztime 60s .PHONY: cover cover: - go test -coverprofile=cover.out ./... + go test -coverpkg=.,./ast,./lexer,./parser,./printer,./scanner,./token -coverprofile=cover.out -modfile=$(TESTMOD) ./... ./testdata .PHONY: cover-html cover-html: cover go tool cover -html=cover.out .PHONY: ycat/build -ycat/build: - go build -o ycat ./cmd/ycat +ycat/build: $(LOCALBIN) + cd ./cmd/ycat && go build -o $(LOCALBIN)/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 + +## Tool Binaries +GOLANGCI_LINT ?= $(LOCALBIN)/golangci-lint + +## Tool Versions +GOLANGCI_VERSION := 2.1.2 + +.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 --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/vendor/github.com/goccy/go-yaml/README.md b/vendor/github.com/goccy/go-yaml/README.md index 945234917..d5ac5b7be 100644 --- a/vendor/github.com/goccy/go-yaml/README.md +++ b/vendor/github.com/goccy/go-yaml/README.md @@ -7,27 +7,71 @@ +## This library has **NO** relation to the go-yaml/yaml library + +> [!IMPORTANT] +> This library is developed from scratch to replace [`go-yaml/yaml`](https://github.com/go-yaml/yaml). +> If you're looking for a better YAML library, this one should be helpful. + # Why a new library? -As of this writing, there already exists a de facto standard library for YAML processing for Go: [https://github.com/go-yaml/yaml](https://github.com/go-yaml/yaml). However we feel that some features are lacking, namely: +As of this writing, there already exists a de facto standard library for YAML processing for Go: [https://github.com/go-yaml/yaml](https://github.com/go-yaml/yaml). However, we believe that a new YAML library is necessary for the following reasons: -- Pretty format for error notifications -- Direct manipulation of YAML abstract syntax tree -- Support for `Anchor` and `Alias` when marshaling -- Allow referencing elements declared in another file via anchors +- Not actively maintained +- `go-yaml/yaml` has ported the libyaml written in C to Go, so the source code is not written in Go style +- There is a lot of content that cannot be parsed +- YAML is often used for configuration, and it is common to include validation along with it. However, the errors in `go-yaml/yaml` are not intuitive, and it is difficult to provide meaningful validation errors +- When creating tools that use YAML, there are cases where reversible transformation of YAML is required. However, to perform reversible transformations of content that includes Comments or Anchors/Aliases, manipulating the AST is the only option +- Non-intuitive [Marshaler](https://pkg.go.dev/gopkg.in/yaml.v3#Marshaler) / [Unmarshaler](https://pkg.go.dev/gopkg.in/yaml.v3#Unmarshaler) + +By the way, libraries such as [ghodss/yaml](https://github.com/ghodss/yaml) and [sigs.k8s.io/yaml](https://github.com/kubernetes-sigs/yaml) also depend on go-yaml/yaml, so if you are using these libraries, the same issues apply: they cannot parse things that go-yaml/yaml cannot parse, and they inherit many of the problems that go-yaml/yaml has. # Features +- No dependencies +- A better parser than `go-yaml/yaml`. + - [Support recursive processing](https://github.com/apple/device-management/blob/release/docs/schema.yaml) + - Higher coverage in the [YAML Test Suite](https://github.com/yaml/yaml-test-suite?tab=readme-ov-file) + - YAML Test Suite consists of 402 cases in total, of which `gopkg.in/yaml.v3` passes `295`. In addition to passing all those test cases, `goccy/go-yaml` successfully passes nearly 60 additional test cases ( 2024/12/15 ) + - The test code is [here](https://github.com/goccy/go-yaml/blob/master/yaml_test_suite_test.go#L77) +- Ease and sustainability of maintenance + - The main maintainer is [@goccy](https://github.com/goccy), but we are also building a system to develop as a team with trusted developers + - Since it is written from scratch, the code is easy to read for Gophers +- An API structure that allows the use of not only `Encoder`/`Decoder` but also `Tokenizer` and `Parser` functionalities. + - [lexer.Tokenize](https://pkg.go.dev/github.com/goccy/go-yaml@v1.15.4/lexer#Tokenize) + - [parser.Parse](https://pkg.go.dev/github.com/goccy/go-yaml@v1.15.4/parser#Parse) +- Filtering, replacing, and merging YAML content using YAML Path +- Reversible transformation without using the AST for YAML that includes Anchors, Aliases, and Comments +- Customize the Marshal/Unmarshal behavior for primitive types and third-party library types ([RegisterCustomMarshaler](https://pkg.go.dev/github.com/goccy/go-yaml#RegisterCustomMarshaler), [RegisterCustomUnmarshaler](https://pkg.go.dev/github.com/goccy/go-yaml#RegisterCustomUnmarshaler)) +- Respects `encoding/json` behavior + - Accept the `json` tag. Note that not all options from the `json` tag will have significance when parsing YAML documents. If both tags exist, `yaml` tag will take precedence. + - [json.Marshaler](https://pkg.go.dev/encoding/json#Marshaler) style [marshaler](https://pkg.go.dev/github.com/goccy/go-yaml#BytesMarshaler) + - [json.Unmarshaler](https://pkg.go.dev/encoding/json#Unmarshaler) style [unmarshaler](https://pkg.go.dev/github.com/goccy/go-yaml#BytesUnmarshaler) + - Options for using `MarshalJSON` and `UnmarshalJSON` ([UseJSONMarshaler](https://pkg.go.dev/github.com/goccy/go-yaml#UseJSONMarshaler), [UseJSONUnmarshaler](https://pkg.go.dev/github.com/goccy/go-yaml#UseJSONUnmarshaler)) - Pretty format for error notifications -- Supports `Scanner` or `Lexer` or `Parser` as public API -- Supports `Anchor` and `Alias` to Marshaler +- Smart validation processing combined with [go-playground/validator](https://github.com/go-playground/validator) + - [example test code is here](https://github.com/goccy/go-yaml/blob/45889c98b0a0967240eb595a1bd6896e2f575106/testdata/validate_test.go#L12) - Allow referencing elements declared in another file via anchors -- Extract value or AST by YAMLPath ( YAMLPath is like a JSONPath ) + +# Users + +The repositories that use goccy/go-yaml are listed here. + +- https://github.com/goccy/go-yaml/wiki/Users + +The source data is [here](https://github.com/goccy/go-yaml/network/dependents). +It is already being used in many repositories. Now it's your turn 😄 + +# Playground + +The Playground visualizes how go-yaml processes YAML text. Use it to assist with your debugging or issue reporting. + +https://goccy.github.io/go-yaml # Installation ```sh -go get -u github.com/goccy/go-yaml +go get github.com/goccy/go-yaml ``` # Synopsis @@ -148,7 +192,9 @@ fmt.Printf("%+v\n", v) // {A:{B:1 C:hello}} ### 3.1. Explicitly declared `Anchor` name and `Alias` name -If you want to use `anchor` or `alias`, you can define it as a struct tag. +If you want to use `anchor`, you can define it as a struct tag. +If the value specified for an anchor is a pointer type and the same address as the pointer is found, the value is automatically set to alias. +If an explicit alias name is specified, an error is raised if its value is different from the value specified in the anchor. ```go type T struct { @@ -178,10 +224,7 @@ d: *x If you do not explicitly declare the anchor name, the default behavior is to use the equivalent of `strings.ToLower($FieldName)` as the name of the anchor. - -If you do not explicitly declare the alias name AND the value is a pointer -to another element, we look up the anchor name by finding out which anchor -field the value is assigned to by looking up its pointer address. +If the value specified for an anchor is a pointer type and the same address as the pointer is found, the value is automatically set to alias. ```go type T struct { @@ -191,8 +234,8 @@ type T struct { var v struct { A *T `yaml:"a,anchor"` B *T `yaml:"b,anchor"` - C *T `yaml:"c,alias"` - D *T `yaml:"d,alias"` + C *T `yaml:"c"` + D *T `yaml:"d"` } v.A = &T{I: 1, S: "hello"} v.B = &T{I: 2, S: "world"} @@ -358,9 +401,16 @@ print yaml file with color ### Installation ```sh -go install github.com/goccy/go-yaml/cmd/ycat@latest +git clone https://github.com/goccy/go-yaml.git +cd go-yaml/cmd/ycat && go install . ``` + +# For Developers + +> [!NOTE] +> In this project, we manage such test code under the `testdata` directory to avoid adding dependencies on libraries that are only needed for testing to the top `go.mod` file. Therefore, if you want to add test cases that use 3rd party libraries, please add the test code to the `testdata` directory. + # Looking for Sponsors I'm looking for sponsors this library. This library is being developed as a personal project in my spare time. If you want a quick response or problem resolution when using this library in your project, please register as a [sponsor](https://github.com/sponsors/goccy). I will cooperate as much as possible. Of course, this library is developed as an MIT license, so you can use it freely for free. diff --git a/vendor/github.com/goccy/go-yaml/ast/ast.go b/vendor/github.com/goccy/go-yaml/ast/ast.go index b4d5ec418..a1a7de1d3 100644 --- a/vendor/github.com/goccy/go-yaml/ast/ast.go +++ b/vendor/github.com/goccy/go-yaml/ast/ast.go @@ -1,6 +1,7 @@ package ast import ( + "errors" "fmt" "io" "math" @@ -8,13 +9,12 @@ import ( "strings" "github.com/goccy/go-yaml/token" - "golang.org/x/xerrors" ) var ( - ErrInvalidTokenType = xerrors.New("invalid token type") - ErrInvalidAnchorName = xerrors.New("invalid anchor name") - ErrInvalidAliasName = xerrors.New("invalid alias name") + ErrInvalidTokenType = errors.New("invalid token type") + ErrInvalidAnchorName = errors.New("invalid anchor name") + ErrInvalidAliasName = errors.New("invalid alias name") ) // NodeType type identifier of node @@ -51,6 +51,8 @@ const ( MappingValueType // SequenceType type identifier for sequence node SequenceType + // SequenceEntryType type identifier for sequence entry node + SequenceEntryType // AnchorType type identifier for anchor node AnchorType // AliasType type identifier for alias node @@ -98,6 +100,8 @@ func (t NodeType) String() string { return "MappingValue" case SequenceType: return "Sequence" + case SequenceEntryType: + return "SequenceEntry" case AnchorType: return "Anchor" case AliasType: @@ -148,6 +152,8 @@ func (t NodeType) YAMLName() string { return "value" case SequenceType: return "sequence" + case SequenceEntryType: + return "value" case AnchorType: return "anchor" case AliasType: @@ -196,6 +202,7 @@ type Node interface { // MapKeyNode type for map key node type MapKeyNode interface { Node + IsMergeKey() bool // String node to text without comment stringWithoutComment() string } @@ -278,6 +285,49 @@ func readNode(p []byte, node Node) (int, error) { return size, nil } +func checkLineBreak(t *token.Token) bool { + if t.Prev != nil { + lbc := "\n" + prev := t.Prev + var adjustment int + // if the previous type is sequence entry use the previous type for that + if prev.Type == token.SequenceEntryType { + // as well as switching to previous type count any new lines in origin to account for: + // - + // b: c + adjustment = strings.Count(strings.TrimRight(t.Origin, lbc), lbc) + if prev.Prev != nil { + prev = prev.Prev + } + } + lineDiff := t.Position.Line - prev.Position.Line - 1 + if lineDiff > 0 { + if prev.Type == token.StringType { + // Remove any line breaks included in multiline string + adjustment += strings.Count(strings.TrimRight(strings.TrimSpace(prev.Origin), lbc), lbc) + } + // Due to the way that comment parsing works its assumed that when a null value does not have new line in origin + // it was squashed therefore difference is ignored. + // foo: + // bar: + // # comment + // baz: 1 + // becomes + // foo: + // bar: null # comment + // + // baz: 1 + if prev.Type == token.NullType || prev.Type == token.ImplicitNullType { + return strings.Count(prev.Origin, lbc) > 0 + } + if lineDiff-adjustment > 0 { + return true + } + } + } + return false +} + // Null create node for null value func Null(tk *token.Token) *NullNode { return &NullNode{ @@ -298,105 +348,30 @@ func Bool(tk *token.Token) *BoolNode { // Integer create node for integer value func Integer(tk *token.Token) *IntegerNode { - value := removeUnderScoreFromNumber(tk.Value) - switch tk.Type { - case token.BinaryIntegerType: - // skip two characters because binary token starts with '0b' - skipCharacterNum := 2 - negativePrefix := "" - if value[0] == '-' { - skipCharacterNum++ - negativePrefix = "-" - } - if len(negativePrefix) > 0 { - i, _ := strconv.ParseInt(negativePrefix+value[skipCharacterNum:], 2, 64) - return &IntegerNode{ - BaseNode: &BaseNode{}, - Token: tk, - Value: i, - } - } - i, _ := strconv.ParseUint(negativePrefix+value[skipCharacterNum:], 2, 64) - return &IntegerNode{ - BaseNode: &BaseNode{}, - Token: tk, - Value: i, - } - case token.OctetIntegerType: - // octet token starts with '0o' or '-0o' or '0' or '-0' - skipCharacterNum := 1 - negativePrefix := "" - if value[0] == '-' { - skipCharacterNum++ - if len(value) > 2 && value[2] == 'o' { - skipCharacterNum++ - } - negativePrefix = "-" - } else { - if value[1] == 'o' { - skipCharacterNum++ - } - } - if len(negativePrefix) > 0 { - i, _ := strconv.ParseInt(negativePrefix+value[skipCharacterNum:], 8, 64) - return &IntegerNode{ - BaseNode: &BaseNode{}, - Token: tk, - Value: i, - } - } - i, _ := strconv.ParseUint(value[skipCharacterNum:], 8, 64) - return &IntegerNode{ - BaseNode: &BaseNode{}, - Token: tk, - Value: i, - } - case token.HexIntegerType: - // hex token starts with '0x' or '-0x' - skipCharacterNum := 2 - negativePrefix := "" - if value[0] == '-' { - skipCharacterNum++ - negativePrefix = "-" - } - if len(negativePrefix) > 0 { - i, _ := strconv.ParseInt(negativePrefix+value[skipCharacterNum:], 16, 64) - return &IntegerNode{ - BaseNode: &BaseNode{}, - Token: tk, - Value: i, - } - } - i, _ := strconv.ParseUint(value[skipCharacterNum:], 16, 64) - return &IntegerNode{ - BaseNode: &BaseNode{}, - Token: tk, - Value: i, - } + var v any + if num := token.ToNumber(tk.Value); num != nil { + v = num.Value } - if value[0] == '-' || value[0] == '+' { - i, _ := strconv.ParseInt(value, 10, 64) - return &IntegerNode{ - BaseNode: &BaseNode{}, - Token: tk, - Value: i, - } - } - i, _ := strconv.ParseUint(value, 10, 64) return &IntegerNode{ BaseNode: &BaseNode{}, Token: tk, - Value: i, + Value: v, } } // Float create node for float value func Float(tk *token.Token) *FloatNode { - f, _ := strconv.ParseFloat(removeUnderScoreFromNumber(tk.Value), 64) + var v float64 + if num := token.ToNumber(tk.Value); num != nil && num.Type == token.NumberTypeFloat { + value, ok := num.Value.(float64) + if ok { + v = value + } + } return &FloatNode{ BaseNode: &BaseNode{}, Token: tk, - Value: f, + Value: v, } } @@ -607,7 +582,9 @@ func (d *DocumentNode) String() string { if d.Start != nil { doc = append(doc, d.Start.Value) } - doc = append(doc, d.Body.String()) + if d.Body != nil { + doc = append(doc, d.Body.String()) + } if d.End != nil { doc = append(doc, d.End.Value) } @@ -619,10 +596,6 @@ func (d *DocumentNode) MarshalYAML() ([]byte, error) { return []byte(d.String()), nil } -func removeUnderScoreFromNumber(num string) string { - return strings.ReplaceAll(num, "_", "") -} - // NullNode type of null node type NullNode struct { *BaseNode @@ -654,6 +627,12 @@ func (n *NullNode) GetValue() interface{} { // String returns `null` text func (n *NullNode) String() string { + if n.Token.Type == token.ImplicitNullType { + if n.Comment != nil { + return n.Comment.String() + } + return "" + } if n.Comment != nil { return addCommentString("null", n.Comment) } @@ -669,6 +648,11 @@ func (n *NullNode) MarshalYAML() ([]byte, error) { return []byte(n.String()), nil } +// IsMergeKey returns whether it is a MergeKey node. +func (n *NullNode) IsMergeKey() bool { + return false +} + // IntegerNode type of integer node type IntegerNode struct { *BaseNode @@ -716,6 +700,11 @@ func (n *IntegerNode) MarshalYAML() ([]byte, error) { return []byte(n.String()), nil } +// IsMergeKey returns whether it is a MergeKey node. +func (n *IntegerNode) IsMergeKey() bool { + return false +} + // FloatNode type of float node type FloatNode struct { *BaseNode @@ -764,6 +753,11 @@ func (n *FloatNode) MarshalYAML() ([]byte, error) { return []byte(n.String()), nil } +// IsMergeKey returns whether it is a MergeKey node. +func (n *FloatNode) IsMergeKey() bool { + return false +} + // StringNode type of string node type StringNode struct { *BaseNode @@ -794,6 +788,11 @@ func (n *StringNode) GetValue() interface{} { return n.Value } +// IsMergeKey returns whether it is a MergeKey node. +func (n *StringNode) IsMergeKey() bool { + return false +} + // escapeSingleQuote escapes s to a single quoted scalar. // https://yaml.org/spec/1.2.2/#732-single-quoted-style func escapeSingleQuote(s string) string { @@ -831,11 +830,12 @@ func (n *StringNode) String() string { // It works mostly, but inconsistencies occur if line break characters are mixed. header := token.LiteralBlockHeader(n.Value) space := strings.Repeat(" ", n.Token.Position.Column-1) + indent := strings.Repeat(" ", n.Token.Position.IndentNum) values := []string{} for _, v := range strings.Split(n.Value, lbc) { - values = append(values, fmt.Sprintf("%s %s", space, v)) + values = append(values, fmt.Sprintf("%s%s%s", space, indent, v)) } - block := strings.TrimSuffix(strings.TrimSuffix(strings.Join(values, lbc), fmt.Sprintf("%s %s", lbc, space)), fmt.Sprintf(" %s", space)) + block := strings.TrimSuffix(strings.TrimSuffix(strings.Join(values, lbc), fmt.Sprintf("%s%s%s", lbc, indent, space)), fmt.Sprintf("%s%s", indent, space)) return fmt.Sprintf("%s%s%s", header, lbc, block) } else if len(n.Value) > 0 && (n.Value[0] == '{' || n.Value[0] == '[') { return fmt.Sprintf(`'%s'`, n.Value) @@ -862,11 +862,12 @@ func (n *StringNode) stringWithoutComment() string { // It works mostly, but inconsistencies occur if line break characters are mixed. header := token.LiteralBlockHeader(n.Value) space := strings.Repeat(" ", n.Token.Position.Column-1) + indent := strings.Repeat(" ", n.Token.Position.IndentNum) values := []string{} for _, v := range strings.Split(n.Value, lbc) { - values = append(values, fmt.Sprintf("%s %s", space, v)) + values = append(values, fmt.Sprintf("%s%s%s", space, indent, v)) } - block := strings.TrimSuffix(strings.TrimSuffix(strings.Join(values, lbc), fmt.Sprintf("%s %s", lbc, space)), fmt.Sprintf(" %s", space)) + block := strings.TrimSuffix(strings.TrimSuffix(strings.Join(values, lbc), fmt.Sprintf("%s%s%s", lbc, indent, space)), fmt.Sprintf(" %s", space)) return fmt.Sprintf("%s%s%s", header, lbc, block) } else if len(n.Value) > 0 && (n.Value[0] == '{' || n.Value[0] == '[') { return fmt.Sprintf(`'%s'`, n.Value) @@ -931,6 +932,11 @@ func (n *LiteralNode) MarshalYAML() ([]byte, error) { return []byte(n.String()), nil } +// IsMergeKey returns whether it is a MergeKey node. +func (n *LiteralNode) IsMergeKey() bool { + return false +} + // MergeKeyNode type of merge key node type MergeKeyNode struct { *BaseNode @@ -974,6 +980,11 @@ func (n *MergeKeyNode) MarshalYAML() ([]byte, error) { return []byte(n.String()), nil } +// IsMergeKey returns whether it is a MergeKey node. +func (n *MergeKeyNode) IsMergeKey() bool { + return true +} + // BoolNode type of boolean node type BoolNode struct { *BaseNode @@ -1021,6 +1032,11 @@ func (n *BoolNode) MarshalYAML() ([]byte, error) { return []byte(n.String()), nil } +// IsMergeKey returns whether it is a MergeKey node. +func (n *BoolNode) IsMergeKey() bool { + return false +} + // InfinityNode type of infinity node type InfinityNode struct { *BaseNode @@ -1068,6 +1084,11 @@ func (n *InfinityNode) MarshalYAML() ([]byte, error) { return []byte(n.String()), nil } +// IsMergeKey returns whether it is a MergeKey node. +func (n *InfinityNode) IsMergeKey() bool { + return false +} + // NanNode type of nan node type NanNode struct { *BaseNode @@ -1114,6 +1135,11 @@ func (n *NanNode) MarshalYAML() ([]byte, error) { return []byte(n.String()), nil } +// IsMergeKey returns whether it is a MergeKey node. +func (n *NanNode) IsMergeKey() bool { + return false +} + // MapNode interface of MappingValueNode / MappingNode type MapNode interface { MapRange() *MapNodeIter @@ -1147,6 +1173,11 @@ func (m *MapNodeIter) Value() Node { return m.values[m.idx].Value } +// KeyValue returns the MappingValueNode of the iterator's current map node entry. +func (m *MapNodeIter) KeyValue() *MappingValueNode { + return m.values[m.idx] +} + // MappingNode type of mapping node type MappingNode struct { *BaseNode @@ -1317,13 +1348,27 @@ func (n *MappingKeyNode) MarshalYAML() ([]byte, error) { return []byte(n.String()), nil } +// IsMergeKey returns whether it is a MergeKey node. +func (n *MappingKeyNode) IsMergeKey() bool { + if n.Value == nil { + return false + } + key, ok := n.Value.(MapKeyNode) + if !ok { + return false + } + return key.IsMergeKey() +} + // MappingValueNode type of mapping value type MappingValueNode struct { *BaseNode - Start *token.Token - Key MapKeyNode - Value Node - FootComment *CommentGroupNode + Start *token.Token // delimiter token ':'. + CollectEntry *token.Token // collect entry token ','. + Key MapKeyNode + Value Node + FootComment *CommentGroupNode + IsFlowStyle bool } // Replace replace value node. @@ -1360,6 +1405,7 @@ func (n *MappingValueNode) AddColumn(col int) { // SetIsFlowStyle set value to IsFlowStyle field recursively. func (n *MappingValueNode) SetIsFlowStyle(isFlow bool) { + n.IsFlowStyle = isFlow switch value := n.Value.(type) { case *MappingNode: value.SetIsFlowStyle(isFlow) @@ -1390,12 +1436,20 @@ func (n *MappingValueNode) String() string { func (n *MappingValueNode) toString() string { space := strings.Repeat(" ", n.Key.GetToken().Position.Column-1) + if checkLineBreak(n.Key.GetToken()) { + space = fmt.Sprintf("%s%s", "\n", space) + } keyIndentLevel := n.Key.GetToken().Position.IndentLevel valueIndentLevel := n.Value.GetToken().Position.IndentLevel keyComment := n.Key.GetComment() if _, ok := n.Value.(ScalarNode); ok { - return fmt.Sprintf("%s%s: %s", space, n.Key.String(), n.Value.String()) - } else if keyIndentLevel < valueIndentLevel { + value := n.Value.String() + if value == "" { + // implicit null value. + return fmt.Sprintf("%s%s:", space, n.Key.String()) + } + return fmt.Sprintf("%s%s: %s", space, n.Key.String(), value) + } else if keyIndentLevel < valueIndentLevel && !n.IsFlowStyle { if keyComment != nil { return fmt.Sprintf( "%s%s: %s\n%s", @@ -1414,7 +1468,10 @@ func (n *MappingValueNode) toString() string { return fmt.Sprintf("%s%s: %s", space, n.Key.String(), n.Value.String()) } else if _, ok := n.Value.(*AliasNode); ok { return fmt.Sprintf("%s%s: %s", space, n.Key.String(), n.Value.String()) + } else if _, ok := n.Value.(*TagNode); ok { + return fmt.Sprintf("%s%s: %s", space, n.Key.String(), n.Value.String()) } + if keyComment != nil { return fmt.Sprintf( "%s%s: %s\n%s", @@ -1485,13 +1542,14 @@ type SequenceNode struct { IsFlowStyle bool Values []Node ValueHeadComments []*CommentGroupNode + Entries []*SequenceEntryNode FootComment *CommentGroupNode } // Replace replace value node. func (n *SequenceNode) Replace(idx int, value Node) error { if len(n.Values) <= idx { - return xerrors.Errorf( + return fmt.Errorf( "invalid index for sequence: sequence length is %d, but specified %d index", len(n.Values), idx, ) @@ -1507,6 +1565,11 @@ func (n *SequenceNode) Merge(target *SequenceNode) { column := n.Start.Position.Column - target.Start.Position.Column target.AddColumn(column) n.Values = append(n.Values, target.Values...) + if len(target.ValueHeadComments) == 0 { + n.ValueHeadComments = append(n.ValueHeadComments, make([]*CommentGroupNode, len(target.Values))...) + return + } + n.ValueHeadComments = append(n.ValueHeadComments, target.ValueHeadComments...) } // SetIsFlowStyle set value to IsFlowStyle field recursively. @@ -1562,7 +1625,15 @@ func (n *SequenceNode) blockStyleString() string { } for idx, value := range n.Values { + if value == nil { + continue + } valueStr := value.String() + newLinePrefix := "" + if strings.HasPrefix(valueStr, "\n") { + valueStr = valueStr[1:] + newLinePrefix = "\n" + } splittedValues := strings.Split(valueStr, "\n") trimmedFirstValue := strings.TrimLeft(splittedValues[0], " ") diffLength := len(splittedValues[0]) - len(trimmedFirstValue) @@ -1585,9 +1656,10 @@ func (n *SequenceNode) blockStyleString() string { } newValue := strings.Join(newValues, "\n") if len(n.ValueHeadComments) == len(n.Values) && n.ValueHeadComments[idx] != nil { - values = append(values, n.ValueHeadComments[idx].StringWithSpace(n.Start.Position.Column-1)) + values = append(values, fmt.Sprintf("%s%s", newLinePrefix, n.ValueHeadComments[idx].StringWithSpace(n.Start.Position.Column-1))) + newLinePrefix = "" } - values = append(values, fmt.Sprintf("%s- %s", space, newValue)) + values = append(values, fmt.Sprintf("%s%s- %s", newLinePrefix, space, newValue)) } if n.FootComment != nil { values = append(values, n.FootComment.StringWithSpace(n.Start.Position.Column-1)) @@ -1616,6 +1688,87 @@ func (n *SequenceNode) MarshalYAML() ([]byte, error) { return []byte(n.String()), nil } +// SequenceEntryNode is the sequence entry. +type SequenceEntryNode struct { + *BaseNode + HeadComment *CommentGroupNode // head comment. + LineComment *CommentGroupNode // line comment e.g.) - # comment. + Start *token.Token // entry token. + Value Node // value node. +} + +// String node to text +func (n *SequenceEntryNode) String() string { + return "" // TODO +} + +// GetToken returns token instance +func (n *SequenceEntryNode) GetToken() *token.Token { + return n.Start +} + +// Type returns type of node +func (n *SequenceEntryNode) Type() NodeType { + return SequenceEntryType +} + +// AddColumn add column number to child nodes recursively +func (n *SequenceEntryNode) AddColumn(col int) { + n.Start.AddColumn(col) +} + +// SetComment set line comment. +func (n *SequenceEntryNode) SetComment(cm *CommentGroupNode) error { + n.LineComment = cm + return nil +} + +// Comment returns comment token instance +func (n *SequenceEntryNode) GetComment() *CommentGroupNode { + return n.LineComment +} + +// MarshalYAML +func (n *SequenceEntryNode) MarshalYAML() ([]byte, error) { + return []byte(n.String()), nil +} + +func (n *SequenceEntryNode) Read(p []byte) (int, error) { + return readNode(p, n) +} + +// SequenceEntry creates SequenceEntryNode instance. +func SequenceEntry(start *token.Token, value Node, headComment *CommentGroupNode) *SequenceEntryNode { + return &SequenceEntryNode{ + BaseNode: &BaseNode{}, + HeadComment: headComment, + Start: start, + Value: value, + } +} + +// SequenceMergeValue creates SequenceMergeValueNode instance. +func SequenceMergeValue(values ...MapNode) *SequenceMergeValueNode { + return &SequenceMergeValueNode{ + values: values, + } +} + +// SequenceMergeValueNode is used to convert the Sequence node specified for the merge key into a MapNode format. +type SequenceMergeValueNode struct { + values []MapNode +} + +// MapRange returns MapNodeIter instance. +func (n *SequenceMergeValueNode) MapRange() *MapNodeIter { + ret := &MapNodeIter{idx: startRangeIndex} + for _, value := range n.values { + iter := value.MapRange() + ret.values = append(ret.values, iter.values...) + } + return ret +} + // AnchorNode type of anchor node type AnchorNode struct { *BaseNode @@ -1624,6 +1777,10 @@ type AnchorNode struct { Value Node } +func (n *AnchorNode) stringWithoutComment() string { + return n.Value.String() +} + func (n *AnchorNode) SetName(name string) error { if n.Name == nil { return ErrInvalidAnchorName @@ -1649,6 +1806,10 @@ func (n *AnchorNode) GetToken() *token.Token { return n.Start } +func (n *AnchorNode) GetValue() any { + return n.Value.GetToken().Value +} + // AddColumn add column number to child nodes recursively func (n *AnchorNode) AddColumn(col int) { n.Start.AddColumn(col) @@ -1662,15 +1823,18 @@ func (n *AnchorNode) AddColumn(col int) { // String anchor to text func (n *AnchorNode) String() string { + anchor := "&" + n.Name.String() value := n.Value.String() - if len(strings.Split(value, "\n")) > 1 { - return fmt.Sprintf("&%s\n%s", n.Name.String(), value) - } else if s, ok := n.Value.(*SequenceNode); ok && !s.IsFlowStyle { - return fmt.Sprintf("&%s\n%s", n.Name.String(), value) + if s, ok := n.Value.(*SequenceNode); ok && !s.IsFlowStyle { + return fmt.Sprintf("%s\n%s", anchor, value) } else if m, ok := n.Value.(*MappingNode); ok && !m.IsFlowStyle { - return fmt.Sprintf("&%s\n%s", n.Name.String(), value) + return fmt.Sprintf("%s\n%s", anchor, value) } - return fmt.Sprintf("&%s %s", n.Name.String(), value) + if value == "" { + // implicit null value. + return anchor + } + return fmt.Sprintf("%s %s", anchor, value) } // MarshalYAML encodes to a YAML text @@ -1678,6 +1842,18 @@ func (n *AnchorNode) MarshalYAML() ([]byte, error) { return []byte(n.String()), nil } +// IsMergeKey returns whether it is a MergeKey node. +func (n *AnchorNode) IsMergeKey() bool { + if n.Value == nil { + return false + } + key, ok := n.Value.(MapKeyNode) + if !ok { + return false + } + return key.IsMergeKey() +} + // AliasNode type of alias node type AliasNode struct { *BaseNode @@ -1685,6 +1861,10 @@ type AliasNode struct { Value Node } +func (n *AliasNode) stringWithoutComment() string { + return n.Value.String() +} + func (n *AliasNode) SetName(name string) error { if n.Value == nil { return ErrInvalidAliasName @@ -1710,6 +1890,10 @@ func (n *AliasNode) GetToken() *token.Token { return n.Start } +func (n *AliasNode) GetValue() any { + return n.Value.GetToken().Value +} + // AddColumn add column number to child nodes recursively func (n *AliasNode) AddColumn(col int) { n.Start.AddColumn(col) @@ -1728,11 +1912,20 @@ func (n *AliasNode) MarshalYAML() ([]byte, error) { return []byte(n.String()), nil } +// IsMergeKey returns whether it is a MergeKey node. +func (n *AliasNode) IsMergeKey() bool { + return false +} + // DirectiveNode type of directive node type DirectiveNode struct { *BaseNode + // Start is '%' token. Start *token.Token - Value Node + // Name is directive name e.g.) "YAML" or "TAG". + Name Node + // Values is directive values e.g.) "1.2" or "!!" and "tag:clarkevans.com,2002:app/". + Values []Node } // Read implements (io.Reader).Read @@ -1750,14 +1943,21 @@ func (n *DirectiveNode) GetToken() *token.Token { // AddColumn add column number to child nodes recursively func (n *DirectiveNode) AddColumn(col int) { - if n.Value != nil { - n.Value.AddColumn(col) + if n.Name != nil { + n.Name.AddColumn(col) + } + for _, value := range n.Values { + value.AddColumn(col) } } // String directive to text func (n *DirectiveNode) String() string { - return fmt.Sprintf("%s%s", n.Start.Value, n.Value.String()) + values := make([]string, 0, len(n.Values)) + for _, val := range n.Values { + values = append(values, val.String()) + } + return strings.Join(append([]string{"%" + n.Name.String()}, values...), " ") } // MarshalYAML encodes to a YAML text @@ -1768,8 +1968,21 @@ func (n *DirectiveNode) MarshalYAML() ([]byte, error) { // TagNode type of tag node type TagNode struct { *BaseNode - Start *token.Token - Value Node + Directive *DirectiveNode + Start *token.Token + Value Node +} + +func (n *TagNode) GetValue() any { + scalar, ok := n.Value.(ScalarNode) + if !ok { + return nil + } + return scalar.GetValue() +} + +func (n *TagNode) stringWithoutComment() string { + return n.Value.String() } // Read implements (io.Reader).Read @@ -1795,7 +2008,14 @@ func (n *TagNode) AddColumn(col int) { // String tag to text func (n *TagNode) String() string { - return fmt.Sprintf("%s %s", n.Start.Value, n.Value.String()) + value := n.Value.String() + if s, ok := n.Value.(*SequenceNode); ok && !s.IsFlowStyle { + return fmt.Sprintf("%s\n%s", n.Start.Value, value) + } else if m, ok := n.Value.(*MappingNode); ok && !m.IsFlowStyle { + return fmt.Sprintf("%s\n%s", n.Start.Value, value) + } + + return fmt.Sprintf("%s %s", n.Start.Value, value) } // MarshalYAML encodes to a YAML text @@ -1803,6 +2023,26 @@ func (n *TagNode) MarshalYAML() ([]byte, error) { return []byte(n.String()), nil } +// IsMergeKey returns whether it is a MergeKey node. +func (n *TagNode) IsMergeKey() bool { + if n.Value == nil { + return false + } + key, ok := n.Value.(MapKeyNode) + if !ok { + return false + } + return key.IsMergeKey() +} + +func (n *TagNode) ArrayRange() *ArrayNodeIter { + arr, ok := n.Value.(ArrayNode) + if !ok { + return nil + } + return arr.ArrayRange() +} + // CommentNode type of comment node type CommentNode struct { *BaseNode @@ -1880,10 +2120,13 @@ func (n *CommentGroupNode) StringWithSpace(col int) string { values := []string{} space := strings.Repeat(" ", col) for _, comment := range n.Comments { + space := space + if checkLineBreak(comment.Token) { + space = fmt.Sprintf("%s%s", "\n", space) + } values = append(values, space+comment.String()) } return strings.Join(values, "\n") - } // MarshalYAML encodes to a YAML text @@ -1930,7 +2173,10 @@ func Walk(v Visitor, node Node) { Walk(v, n.Value) case *DirectiveNode: walkComment(v, n.BaseNode) - Walk(v, n.Value) + Walk(v, n.Name) + for _, value := range n.Values { + Walk(v, value) + } case *TagNode: walkComment(v, n.BaseNode) Walk(v, n.Value) @@ -2016,7 +2262,14 @@ func (f *parentFinder) walk(parent, node Node) Node { case *LiteralNode: return f.walk(node, n.Value) case *DirectiveNode: - return f.walk(node, n.Value) + if found := f.walk(node, n.Name); found != nil { + return found + } + for _, value := range n.Values { + if found := f.walk(node, value); found != nil { + return found + } + } case *TagNode: return f.walk(node, n.Value) case *DocumentNode: @@ -2092,10 +2345,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 +2356,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/vendor/github.com/goccy/go-yaml/context.go b/vendor/github.com/goccy/go-yaml/context.go new file mode 100644 index 000000000..133f05ee5 --- /dev/null +++ b/vendor/github.com/goccy/go-yaml/context.go @@ -0,0 +1,37 @@ +package yaml + +import "context" + +type ( + ctxMergeKey struct{} + ctxAnchorKey struct{} +) + +func withMerge(ctx context.Context) context.Context { + return context.WithValue(ctx, ctxMergeKey{}, true) +} + +func isMerge(ctx context.Context) bool { + v, ok := ctx.Value(ctxMergeKey{}).(bool) + if !ok { + return false + } + return v +} + +func withAnchor(ctx context.Context, name string) context.Context { + anchorMap := getAnchorMap(ctx) + if anchorMap == nil { + anchorMap = make(map[string]struct{}) + } + anchorMap[name] = struct{}{} + return context.WithValue(ctx, ctxAnchorKey{}, anchorMap) +} + +func getAnchorMap(ctx context.Context) map[string]struct{} { + v, ok := ctx.Value(ctxAnchorKey{}).(map[string]struct{}) + if !ok { + return nil + } + return v +} diff --git a/vendor/github.com/goccy/go-yaml/decode.go b/vendor/github.com/goccy/go-yaml/decode.go index 72af5e22d..19414dd2f 100644 --- a/vendor/github.com/goccy/go-yaml/decode.go +++ b/vendor/github.com/goccy/go-yaml/decode.go @@ -7,18 +7,19 @@ import ( "encoding/base64" "fmt" "io" + "maps" "math" "os" "path/filepath" "reflect" "sort" "strconv" + "strings" "time" - "golang.org/x/xerrors" - "github.com/goccy/go-yaml/ast" "github.com/goccy/go-yaml/internal/errors" + "github.com/goccy/go-yaml/internal/format" "github.com/goccy/go-yaml/parser" "github.com/goccy/go-yaml/token" ) @@ -29,7 +30,8 @@ type Decoder struct { referenceReaders []io.Reader anchorNodeMap map[string]ast.Node anchorValueMap map[string]reflect.Value - customUnmarshalerMap map[reflect.Type]func(interface{}, []byte) error + customUnmarshalerMap map[reflect.Type]func(context.Context, interface{}, []byte) error + commentMaps []CommentMap toCommentMap CommentMap opts []DecodeOption referenceFiles []string @@ -38,11 +40,12 @@ type Decoder struct { isResolvedReference bool validator StructValidator disallowUnknownField bool - disallowDuplicateKey bool + allowDuplicateMapKey bool useOrderedMap bool useJSONUnmarshaler bool parsedFile *ast.File streamIndex int + decodeDepth int } // NewDecoder returns a new decoder that reads from r. @@ -51,7 +54,7 @@ func NewDecoder(r io.Reader, opts ...DecodeOption) *Decoder { reader: r, anchorNodeMap: map[string]ast.Node{}, anchorValueMap: map[string]reflect.Value{}, - customUnmarshalerMap: map[reflect.Type]func(interface{}, []byte) error{}, + customUnmarshalerMap: map[reflect.Type]func(context.Context, interface{}, []byte) error{}, opts: opts, referenceReaders: []io.Reader{}, referenceFiles: []string{}, @@ -59,11 +62,25 @@ func NewDecoder(r io.Reader, opts ...DecodeOption) *Decoder { isRecursiveDir: false, isResolvedReference: false, disallowUnknownField: false, - disallowDuplicateKey: false, + allowDuplicateMapKey: false, useOrderedMap: false, } } +const maxDecodeDepth = 10000 + +func (d *Decoder) stepIn() { + d.decodeDepth++ +} + +func (d *Decoder) stepOut() { + d.decodeDepth-- +} + +func (d *Decoder) isExceededMaxDepth() bool { + return d.decodeDepth > maxDecodeDepth +} + func (d *Decoder) castToFloat(v interface{}) interface{} { switch vv := v.(type) { case int: @@ -98,63 +115,111 @@ func (d *Decoder) castToFloat(v interface{}) interface{} { return 0 } -func (d *Decoder) mergeValueNode(value ast.Node) ast.Node { - if value.Type() == ast.AliasType { - aliasNode := value.(*ast.AliasNode) - aliasName := aliasNode.Value.GetToken().Value - return d.anchorNodeMap[aliasName] +func (d *Decoder) mapKeyNodeToString(ctx context.Context, node ast.MapKeyNode) (string, error) { + key, err := d.nodeToValue(ctx, node) + if err != nil { + return "", err } - return value -} - -func (d *Decoder) mapKeyNodeToString(node ast.MapKeyNode) string { - key := d.nodeToValue(node) if key == nil { - return "null" + return "null", nil } if k, ok := key.(string); ok { - return k + return k, nil } - return fmt.Sprint(key) + return fmt.Sprint(key), nil } -func (d *Decoder) setToMapValue(node ast.Node, m map[string]interface{}) { +func (d *Decoder) setToMapValue(ctx context.Context, node ast.Node, m map[string]interface{}) error { + d.stepIn() + defer d.stepOut() + if d.isExceededMaxDepth() { + return ErrExceededMaxDepth + } + d.setPathToCommentMap(node) switch n := node.(type) { case *ast.MappingValueNode: - if n.Key.Type() == ast.MergeKeyType { - d.setToMapValue(d.mergeValueNode(n.Value), m) + if n.Key.IsMergeKey() { + value, err := d.getMapNode(n.Value, true) + if err != nil { + return err + } + iter := value.MapRange() + for iter.Next() { + if err := d.setToMapValue(ctx, iter.KeyValue(), m); err != nil { + return err + } + } } else { - key := d.mapKeyNodeToString(n.Key) - m[key] = d.nodeToValue(n.Value) + key, err := d.mapKeyNodeToString(ctx, n.Key) + if err != nil { + return err + } + v, err := d.nodeToValue(ctx, n.Value) + if err != nil { + return err + } + m[key] = v } case *ast.MappingNode: for _, value := range n.Values { - d.setToMapValue(value, m) + if err := d.setToMapValue(ctx, value, m); err != nil { + return err + } } case *ast.AnchorNode: anchorName := n.Name.GetToken().Value d.anchorNodeMap[anchorName] = n.Value } + return nil } -func (d *Decoder) setToOrderedMapValue(node ast.Node, m *MapSlice) { +func (d *Decoder) setToOrderedMapValue(ctx context.Context, node ast.Node, m *MapSlice) error { + d.stepIn() + defer d.stepOut() + if d.isExceededMaxDepth() { + return ErrExceededMaxDepth + } + + d.setPathToCommentMap(node) switch n := node.(type) { case *ast.MappingValueNode: - if n.Key.Type() == ast.MergeKeyType { - d.setToOrderedMapValue(d.mergeValueNode(n.Value), m) + if n.Key.IsMergeKey() { + value, err := d.getMapNode(n.Value, true) + if err != nil { + return err + } + iter := value.MapRange() + for iter.Next() { + if err := d.setToOrderedMapValue(ctx, iter.KeyValue(), m); err != nil { + return err + } + } } else { - key := d.mapKeyNodeToString(n.Key) - *m = append(*m, MapItem{Key: key, Value: d.nodeToValue(n.Value)}) + key, err := d.mapKeyNodeToString(ctx, n.Key) + if err != nil { + return err + } + value, err := d.nodeToValue(ctx, n.Value) + if err != nil { + return err + } + *m = append(*m, MapItem{Key: key, Value: value}) } case *ast.MappingNode: for _, value := range n.Values { - d.setToOrderedMapValue(value, m) + if err := d.setToOrderedMapValue(ctx, value, m); err != nil { + return err + } } } + return nil } func (d *Decoder) setPathToCommentMap(node ast.Node) { + if node == nil { + return + } if d.toCommentMap == nil { return } @@ -186,6 +251,14 @@ func (d *Decoder) addHeadOrLineCommentToMap(node ast.Node) { } commentPath := node.GetPath() if minCommentLine < targetLine { + switch n := node.(type) { + case *ast.MappingNode: + if len(n.Values) != 0 { + commentPath = n.Values[0].Key.GetPath() + } + case *ast.MappingValueNode: + commentPath = n.Key.GetPath() + } d.addCommentToMap(commentPath, HeadComment(texts...)) } else { d.addCommentToMap(commentPath, LineComment(texts[0])) @@ -222,18 +295,24 @@ func (d *Decoder) addSequenceNodeCommentToMap(node *ast.SequenceNode) { func (d *Decoder) addFootCommentToMap(node ast.Node) { var ( footComment *ast.CommentGroupNode - footCommentPath string = node.GetPath() + footCommentPath = node.GetPath() ) switch n := node.(type) { case *ast.SequenceNode: - if len(n.Values) != 0 { - footCommentPath = n.Values[len(n.Values)-1].GetPath() - } footComment = n.FootComment + if n.FootComment != nil { + footCommentPath = n.FootComment.GetPath() + } case *ast.MappingNode: footComment = n.FootComment + if n.FootComment != nil { + footCommentPath = n.FootComment.GetPath() + } case *ast.MappingValueNode: footComment = n.FootComment + if n.FootComment != nil { + footCommentPath = n.FootComment.GetPath() + } } if footComment == nil { return @@ -260,192 +339,251 @@ func (d *Decoder) addCommentToMap(path string, comment *Comment) { }) } -func (d *Decoder) nodeToValue(node ast.Node) interface{} { +func (d *Decoder) nodeToValue(ctx context.Context, node ast.Node) (any, error) { + d.stepIn() + defer d.stepOut() + if d.isExceededMaxDepth() { + return nil, ErrExceededMaxDepth + } + d.setPathToCommentMap(node) switch n := node.(type) { case *ast.NullNode: - return nil + return nil, nil case *ast.StringNode: - return n.GetValue() + return n.GetValue(), nil case *ast.IntegerNode: - return n.GetValue() + return n.GetValue(), nil case *ast.FloatNode: - return n.GetValue() + return n.GetValue(), nil case *ast.BoolNode: - return n.GetValue() + return n.GetValue(), nil case *ast.InfinityNode: - return n.GetValue() + return n.GetValue(), nil case *ast.NanNode: - return n.GetValue() + return n.GetValue(), nil case *ast.TagNode: + if n.Directive != nil { + v, err := d.nodeToValue(ctx, n.Value) + if err != nil { + return nil, err + } + if v == nil { + return "", nil + } + return fmt.Sprint(v), nil + } switch token.ReservedTagKeyword(n.Start.Value) { case token.TimestampTag: - t, _ := d.castToTime(n.Value) - return t + t, _ := d.castToTime(ctx, n.Value) + return t, nil case token.IntegerTag: - i, _ := strconv.Atoi(fmt.Sprint(d.nodeToValue(n.Value))) - return i + v, err := d.nodeToValue(ctx, n.Value) + if err != nil { + return nil, err + } + i, _ := strconv.Atoi(fmt.Sprint(v)) + return i, nil case token.FloatTag: - return d.castToFloat(d.nodeToValue(n.Value)) + v, err := d.nodeToValue(ctx, n.Value) + if err != nil { + return nil, err + } + return d.castToFloat(v), nil case token.NullTag: - return nil + return nil, nil case token.BinaryTag: - b, _ := base64.StdEncoding.DecodeString(d.nodeToValue(n.Value).(string)) - return b + v, err := d.nodeToValue(ctx, n.Value) + if err != nil { + return nil, err + } + str, ok := v.(string) + if !ok { + return nil, errors.ErrSyntax( + fmt.Sprintf("cannot convert %q to string", fmt.Sprint(v)), + n.Value.GetToken(), + ) + } + b, _ := base64.StdEncoding.DecodeString(str) + return b, nil + case token.BooleanTag: + v, err := d.nodeToValue(ctx, n.Value) + if err != nil { + return nil, err + } + str := strings.ToLower(fmt.Sprint(v)) + b, err := strconv.ParseBool(str) + if err == nil { + return b, nil + } + switch str { + case "yes": + return true, nil + case "no": + return false, nil + } + return nil, errors.ErrSyntax(fmt.Sprintf("cannot convert %q to boolean", fmt.Sprint(v)), n.Value.GetToken()) case token.StringTag: - return d.nodeToValue(n.Value) + v, err := d.nodeToValue(ctx, n.Value) + if err != nil { + return nil, err + } + if v == nil { + return "", nil + } + return fmt.Sprint(v), nil case token.MappingTag: - return d.nodeToValue(n.Value) + return d.nodeToValue(ctx, n.Value) + default: + return d.nodeToValue(ctx, n.Value) } case *ast.AnchorNode: anchorName := n.Name.GetToken().Value - anchorValue := d.nodeToValue(n.Value) + + // To handle the case where alias is processed recursively, the result of alias can be set to nil in advance. + d.anchorNodeMap[anchorName] = nil + anchorValue, err := d.nodeToValue(withAnchor(ctx, anchorName), n.Value) + if err != nil { + delete(d.anchorNodeMap, anchorName) + return nil, err + } d.anchorNodeMap[anchorName] = n.Value - return anchorValue + d.anchorValueMap[anchorName] = reflect.ValueOf(anchorValue) + return anchorValue, nil case *ast.AliasNode: + text := n.Value.String() + if _, exists := getAnchorMap(ctx)[text]; exists { + // self recursion. + return nil, nil + } + if v, exists := d.anchorValueMap[text]; exists { + if !v.IsValid() { + return nil, nil + } + return v.Interface(), nil + } aliasName := n.Value.GetToken().Value - node := d.anchorNodeMap[aliasName] - return d.nodeToValue(node) + return nil, errors.ErrSyntax(fmt.Sprintf("could not find alias %q", aliasName), n.Value.GetToken()) case *ast.LiteralNode: - return n.Value.GetValue() + return n.Value.GetValue(), nil case *ast.MappingKeyNode: - return d.nodeToValue(n.Value) + return d.nodeToValue(ctx, n.Value) case *ast.MappingValueNode: - if n.Key.Type() == ast.MergeKeyType { - value := d.mergeValueNode(n.Value) + if n.Key.IsMergeKey() { + value, err := d.getMapNode(n.Value, true) + if err != nil { + return nil, err + } + iter := value.MapRange() if d.useOrderedMap { m := MapSlice{} - d.setToOrderedMapValue(value, &m) - return m + for iter.Next() { + if err := d.setToOrderedMapValue(ctx, iter.KeyValue(), &m); err != nil { + return nil, err + } + } + return m, nil } - m := map[string]interface{}{} - d.setToMapValue(value, m) - return m + m := make(map[string]any) + for iter.Next() { + if err := d.setToMapValue(ctx, iter.KeyValue(), m); err != nil { + return nil, err + } + } + return m, nil + } + key, err := d.mapKeyNodeToString(ctx, n.Key) + if err != nil { + return nil, err } - key := d.mapKeyNodeToString(n.Key) if d.useOrderedMap { - return MapSlice{{Key: key, Value: d.nodeToValue(n.Value)}} + v, err := d.nodeToValue(ctx, n.Value) + if err != nil { + return nil, err + } + return MapSlice{{Key: key, Value: v}}, nil } - return map[string]interface{}{ - key: d.nodeToValue(n.Value), + v, err := d.nodeToValue(ctx, n.Value) + if err != nil { + return nil, err } + return map[string]interface{}{key: v}, nil case *ast.MappingNode: if d.useOrderedMap { m := make(MapSlice, 0, len(n.Values)) for _, value := range n.Values { - d.setToOrderedMapValue(value, &m) + if err := d.setToOrderedMapValue(ctx, value, &m); err != nil { + return nil, err + } } - return m + return m, nil } m := make(map[string]interface{}, len(n.Values)) for _, value := range n.Values { - d.setToMapValue(value, m) + if err := d.setToMapValue(ctx, value, m); err != nil { + return nil, err + } } - return m + return m, nil case *ast.SequenceNode: v := make([]interface{}, 0, len(n.Values)) for _, value := range n.Values { - v = append(v, d.nodeToValue(value)) + vv, err := d.nodeToValue(ctx, value) + if err != nil { + return nil, err + } + v = append(v, vv) } - return v + return v, nil } - return nil + return nil, nil } -func (d *Decoder) resolveAlias(node ast.Node) (ast.Node, error) { +func (d *Decoder) getMapNode(node ast.Node, isMerge bool) (ast.MapNode, error) { + d.stepIn() + defer d.stepOut() + if d.isExceededMaxDepth() { + return nil, ErrExceededMaxDepth + } + switch n := node.(type) { - case *ast.MappingNode: - for idx, v := range n.Values { - value, err := d.resolveAlias(v) - if err != nil { - return nil, err - } - n.Values[idx] = value.(*ast.MappingValueNode) - } - case *ast.TagNode: - value, err := d.resolveAlias(n.Value) - if err != nil { - return nil, err - } - n.Value = value - case *ast.MappingKeyNode: - value, err := d.resolveAlias(n.Value) - if err != nil { - return nil, err - } - n.Value = value - case *ast.MappingValueNode: - if n.Key.Type() == ast.MergeKeyType && n.Value.Type() == ast.AliasType { - value, err := d.resolveAlias(n.Value) - if err != nil { - return nil, err - } - keyColumn := n.Key.GetToken().Position.Column - requiredColumn := keyColumn + 2 - value.AddColumn(requiredColumn) - n.Value = value - } else { - key, err := d.resolveAlias(n.Key) - if err != nil { - return nil, err - } - n.Key = key.(ast.MapKeyNode) - value, err := d.resolveAlias(n.Value) - if err != nil { - return nil, err - } - n.Value = value - } - case *ast.SequenceNode: - for idx, v := range n.Values { - value, err := d.resolveAlias(v) - if err != nil { - return nil, err - } - n.Values[idx] = value - } + case ast.MapNode: + return n, nil + case *ast.AnchorNode: + anchorName := n.Name.GetToken().Value + d.anchorNodeMap[anchorName] = n.Value + return d.getMapNode(n.Value, isMerge) case *ast.AliasNode: aliasName := n.Value.GetToken().Value node := d.anchorNodeMap[aliasName] if node == nil { - return nil, xerrors.Errorf("cannot find anchor by alias name %s", aliasName) + return nil, fmt.Errorf("cannot find anchor by alias name %s", aliasName) } - return d.resolveAlias(node) - } - return node, nil -} - -func (d *Decoder) getMapNode(node ast.Node) (ast.MapNode, error) { - if _, ok := node.(*ast.NullNode); ok { - return nil, nil - } - if anchor, ok := node.(*ast.AnchorNode); ok { - mapNode, ok := anchor.Value.(ast.MapNode) - if ok { - return mapNode, nil + return d.getMapNode(node, isMerge) + case *ast.SequenceNode: + if !isMerge { + return nil, errors.ErrUnexpectedNodeType(node.Type(), ast.MappingType, node.GetToken()) } - return nil, errUnexpectedNodeType(anchor.Value.Type(), ast.MappingType, node.GetToken()) - } - if alias, ok := node.(*ast.AliasNode); ok { - aliasName := alias.Value.GetToken().Value - node := d.anchorNodeMap[aliasName] - if node == nil { - return nil, xerrors.Errorf("cannot find anchor by alias name %s", aliasName) + var mapNodes []ast.MapNode + for _, value := range n.Values { + mapNode, err := d.getMapNode(value, false) + if err != nil { + return nil, err + } + mapNodes = append(mapNodes, mapNode) } - mapNode, ok := node.(ast.MapNode) - if ok { - return mapNode, nil - } - return nil, errUnexpectedNodeType(node.Type(), ast.MappingType, node.GetToken()) + return ast.SequenceMergeValue(mapNodes...), nil } - mapNode, ok := node.(ast.MapNode) - if !ok { - return nil, errUnexpectedNodeType(node.Type(), ast.MappingType, node.GetToken()) - } - return mapNode, nil + return nil, errors.ErrUnexpectedNodeType(node.Type(), ast.MappingType, node.GetToken()) } func (d *Decoder) getArrayNode(node ast.Node) (ast.ArrayNode, error) { + d.stepIn() + defer d.stepOut() + if d.isExceededMaxDepth() { + return nil, ErrExceededMaxDepth + } + if _, ok := node.(*ast.NullNode); ok { return nil, nil } @@ -455,36 +593,27 @@ func (d *Decoder) getArrayNode(node ast.Node) (ast.ArrayNode, error) { return arrayNode, nil } - return nil, errUnexpectedNodeType(anchor.Value.Type(), ast.SequenceType, node.GetToken()) + return nil, errors.ErrUnexpectedNodeType(anchor.Value.Type(), ast.SequenceType, node.GetToken()) } if alias, ok := node.(*ast.AliasNode); ok { aliasName := alias.Value.GetToken().Value node := d.anchorNodeMap[aliasName] if node == nil { - return nil, xerrors.Errorf("cannot find anchor by alias name %s", aliasName) + return nil, fmt.Errorf("cannot find anchor by alias name %s", aliasName) } arrayNode, ok := node.(ast.ArrayNode) if ok { return arrayNode, nil } - return nil, errUnexpectedNodeType(node.Type(), ast.SequenceType, node.GetToken()) + return nil, errors.ErrUnexpectedNodeType(node.Type(), ast.SequenceType, node.GetToken()) } arrayNode, ok := node.(ast.ArrayNode) if !ok { - return nil, errUnexpectedNodeType(node.Type(), ast.SequenceType, node.GetToken()) + return nil, errors.ErrUnexpectedNodeType(node.Type(), ast.SequenceType, node.GetToken()) } 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) { @@ -503,70 +632,34 @@ func (d *Decoder) convertValue(v reflect.Value, typ reflect.Type, src ast.Node) // else, fall through to the error below } } - return reflect.Zero(typ), errTypeMismatch(typ, v.Type(), src.GetToken()) + return reflect.Zero(typ), errors.ErrTypeMismatch(typ, v.Type(), src.GetToken()) } return v.Convert(typ), nil } // cast value to string + var strVal string switch v.Type().Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return reflect.ValueOf(fmt.Sprint(v.Int())), nil + strVal = strconv.FormatInt(v.Int(), 10) case reflect.Float32, reflect.Float64: - return reflect.ValueOf(fmt.Sprint(v.Float())), nil + strVal = fmt.Sprint(v.Float()) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - return reflect.ValueOf(fmt.Sprint(v.Uint())), nil + strVal = strconv.FormatUint(v.Uint(), 10) case reflect.Bool: - return reflect.ValueOf(fmt.Sprint(v.Bool())), nil + strVal = strconv.FormatBool(v.Bool()) + default: + if !v.Type().ConvertibleTo(typ) { + return reflect.Zero(typ), errors.ErrTypeMismatch(typ, v.Type(), src.GetToken()) + } + return v.Convert(typ), nil } - if !v.Type().ConvertibleTo(typ) { - return reflect.Zero(typ), errTypeMismatch(typ, v.Type(), src.GetToken()) + + val := reflect.ValueOf(strVal) + if val.Type() != typ { + // Handle named types, e.g., `type MyString string` + val = val.Convert(typ) } - 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} -} - -type unknownFieldError struct { - err error -} - -func (e *unknownFieldError) Error() string { - return e.err.Error() -} - -func errUnknownField(msg string, tk *token.Token) *unknownFieldError { - return &unknownFieldError{err: errors.ErrSyntax(msg, tk)} -} - -func errUnexpectedNodeType(actual, expected ast.NodeType, tk *token.Token) error { - return errors.ErrSyntax(fmt.Sprintf("%s was used where %s is expected", actual.YAMLName(), expected.YAMLName()), tk) -} - -type duplicateKeyError struct { - err error -} - -func (e *duplicateKeyError) Error() string { - return e.err.Error() -} - -func errDuplicateKey(msg string, tk *token.Token) *duplicateKeyError { - return &duplicateKeyError{err: errors.ErrSyntax(msg, tk)} + return val, nil } func (d *Decoder) deleteStructKeys(structType reflect.Type, unknownFields map[string]ast.Node) error { @@ -575,7 +668,7 @@ func (d *Decoder) deleteStructKeys(structType reflect.Type, unknownFields map[st } structFieldMap, err := structFieldMap(structType) if err != nil { - return errors.Wrapf(err, "failed to create struct field map") + return err } for j := 0; j < structType.NumField(); j++ { @@ -590,7 +683,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) } @@ -598,57 +691,18 @@ func (d *Decoder) deleteStructKeys(structType reflect.Type, unknownFields map[st return nil } -func (d *Decoder) lastNode(node ast.Node) ast.Node { - switch n := node.(type) { - case *ast.MappingNode: - if len(n.Values) > 0 { - return d.lastNode(n.Values[len(n.Values)-1]) - } - case *ast.MappingValueNode: - return d.lastNode(n.Value) - case *ast.SequenceNode: - if len(n.Values) > 0 { - return d.lastNode(n.Values[len(n.Values)-1]) - } - } - return node -} - func (d *Decoder) unmarshalableDocument(node ast.Node) ([]byte, error) { - var err error - node, err = d.resolveAlias(node) - if err != nil { - return nil, err - } - doc := node.String() - last := d.lastNode(node) - if last != nil && last.Type() == ast.LiteralType { - doc += "\n" - } + doc := format.FormatNodeWithResolvedAlias(node, d.anchorNodeMap) return []byte(doc), nil } -func (d *Decoder) unmarshalableText(node ast.Node) ([]byte, bool, error) { - var err error - node, err = d.resolveAlias(node) - if err != nil { - return nil, false, err +func (d *Decoder) unmarshalableText(node ast.Node) ([]byte, bool) { + doc := format.FormatNodeWithResolvedAlias(node, d.anchorNodeMap) + var v string + if err := Unmarshal([]byte(doc), &v); err != nil { + return nil, false } - if node.Type() == ast.AnchorType { - node = node.(*ast.AnchorNode).Value - } - switch n := node.(type) { - case *ast.StringNode: - return []byte(n.Value), true, nil - case *ast.LiteralNode: - return []byte(n.Value.GetToken().Value), true, nil - default: - scalar, ok := n.(ast.ScalarNode) - if ok { - return []byte(fmt.Sprint(scalar.GetValue())), true, nil - } - } - return nil, false, nil + return []byte(v), true } type jsonUnmarshaler interface { @@ -668,7 +722,7 @@ func (d *Decoder) existsTypeInCustomUnmarshalerMap(t reflect.Type) bool { return false } -func (d *Decoder) unmarshalerFromCustomUnmarshalerMap(t reflect.Type) (func(interface{}, []byte) error, bool) { +func (d *Decoder) unmarshalerFromCustomUnmarshalerMap(t reflect.Type) (func(context.Context, interface{}, []byte) error, bool) { if unmarshaler, exists := d.customUnmarshalerMap[t]; exists { return unmarshaler, exists } @@ -688,19 +742,15 @@ func (d *Decoder) canDecodeByUnmarshaler(dst reflect.Value) bool { } iface := ptrValue.Interface() switch iface.(type) { - case BytesUnmarshalerContext: - return true - case BytesUnmarshaler: - return true - case InterfaceUnmarshalerContext: - return true - case InterfaceUnmarshaler: - return true - case *time.Time: - return true - case *time.Duration: - return true - case encoding.TextUnmarshaler: + case BytesUnmarshalerContext, + BytesUnmarshaler, + InterfaceUnmarshalerContext, + InterfaceUnmarshaler, + NodeUnmarshaler, + NodeUnmarshalerContext, + *time.Time, + *time.Duration, + encoding.TextUnmarshaler: return true case jsonUnmarshaler: return d.useJSONUnmarshaler @@ -713,10 +763,10 @@ func (d *Decoder) decodeByUnmarshaler(ctx context.Context, dst reflect.Value, sr if unmarshaler, exists := d.unmarshalerFromCustomUnmarshalerMap(ptrValue.Type()); exists { b, err := d.unmarshalableDocument(src) if err != nil { - return errors.Wrapf(err, "failed to UnmarshalYAML") + return err } - if err := unmarshaler(ptrValue.Interface(), b); err != nil { - return errors.Wrapf(err, "failed to UnmarshalYAML") + if err := unmarshaler(ctx, ptrValue.Interface(), b); err != nil { + return err } return nil } @@ -725,10 +775,10 @@ func (d *Decoder) decodeByUnmarshaler(ctx context.Context, dst reflect.Value, sr if unmarshaler, ok := iface.(BytesUnmarshalerContext); ok { b, err := d.unmarshalableDocument(src) if err != nil { - return errors.Wrapf(err, "failed to UnmarshalYAML") + return err } if err := unmarshaler.UnmarshalYAML(ctx, b); err != nil { - return errors.Wrapf(err, "failed to UnmarshalYAML") + return err } return nil } @@ -736,10 +786,10 @@ func (d *Decoder) decodeByUnmarshaler(ctx context.Context, dst reflect.Value, sr if unmarshaler, ok := iface.(BytesUnmarshaler); ok { b, err := d.unmarshalableDocument(src) if err != nil { - return errors.Wrapf(err, "failed to UnmarshalYAML") + return err } if err := unmarshaler.UnmarshalYAML(b); err != nil { - return errors.Wrapf(err, "failed to UnmarshalYAML") + return err } return nil } @@ -748,14 +798,14 @@ func (d *Decoder) decodeByUnmarshaler(ctx context.Context, dst reflect.Value, sr if err := unmarshaler.UnmarshalYAML(ctx, func(v interface{}) error { rv := reflect.ValueOf(v) if rv.Type().Kind() != reflect.Ptr { - return errors.ErrDecodeRequiredPointerType + return ErrDecodeRequiredPointerType } if err := d.decodeValue(ctx, rv.Elem(), src); err != nil { - return errors.Wrapf(err, "failed to decode value") + return err } return nil }); err != nil { - return errors.Wrapf(err, "failed to UnmarshalYAML") + return err } return nil } @@ -764,18 +814,34 @@ func (d *Decoder) decodeByUnmarshaler(ctx context.Context, dst reflect.Value, sr if err := unmarshaler.UnmarshalYAML(func(v interface{}) error { rv := reflect.ValueOf(v) if rv.Type().Kind() != reflect.Ptr { - return errors.ErrDecodeRequiredPointerType + return ErrDecodeRequiredPointerType } if err := d.decodeValue(ctx, rv.Elem(), src); err != nil { - return errors.Wrapf(err, "failed to decode value") + return err } return nil }); err != nil { - return errors.Wrapf(err, "failed to UnmarshalYAML") + return err } return nil } + if unmarshaler, ok := iface.(NodeUnmarshaler); ok { + if err := unmarshaler.UnmarshalYAML(src); err != nil { + return err + } + + return nil + } + + if unmarshaler, ok := iface.(NodeUnmarshalerContext); ok { + if err := unmarshaler.UnmarshalYAML(ctx, src); err != nil { + return err + } + + return nil + } + if _, ok := iface.(*time.Time); ok { return d.decodeTime(ctx, dst, src) } @@ -785,13 +851,10 @@ func (d *Decoder) decodeByUnmarshaler(ctx context.Context, dst reflect.Value, sr } if unmarshaler, isText := iface.(encoding.TextUnmarshaler); isText { - b, ok, err := d.unmarshalableText(src) - if err != nil { - return errors.Wrapf(err, "failed to UnmarshalText") - } + b, ok := d.unmarshalableText(src) if ok { if err := unmarshaler.UnmarshalText(b); err != nil { - return errors.Wrapf(err, "failed to UnmarshalText") + return err } return nil } @@ -801,21 +864,21 @@ func (d *Decoder) decodeByUnmarshaler(ctx context.Context, dst reflect.Value, sr if unmarshaler, ok := iface.(jsonUnmarshaler); ok { b, err := d.unmarshalableDocument(src) if err != nil { - return errors.Wrapf(err, "failed to UnmarshalJSON") + return err } jsonBytes, err := YAMLToJSON(b) if err != nil { - return errors.Wrapf(err, "failed to convert yaml to json") + return err } jsonBytes = bytes.TrimRight(jsonBytes, "\n") if err := unmarshaler.UnmarshalJSON(jsonBytes); err != nil { - return errors.Wrapf(err, "failed to UnmarshalJSON") + return err } return nil } } - return xerrors.Errorf("does not implemented Unmarshaler") + return errors.New("does not implemented Unmarshaler") } var ( @@ -823,15 +886,27 @@ var ( ) func (d *Decoder) decodeValue(ctx context.Context, dst reflect.Value, src ast.Node) error { + d.stepIn() + defer d.stepOut() + if d.isExceededMaxDepth() { + return ErrExceededMaxDepth + } + if !dst.IsValid() { + return nil + } + if src.Type() == ast.AnchorType { - anchorName := src.(*ast.AnchorNode).Name.GetToken().Value - if _, exists := d.anchorValueMap[anchorName]; !exists { - d.anchorValueMap[anchorName] = dst + anchor, _ := src.(*ast.AnchorNode) + anchorName := anchor.Name.GetToken().Value + if err := d.decodeValue(withAnchor(ctx, anchorName), dst, anchor.Value); err != nil { + return err } + d.anchorValueMap[anchorName] = dst + return nil } if d.canDecodeByUnmarshaler(dst) { if err := d.decodeByUnmarshaler(ctx, dst, src); err != nil { - return errors.Wrapf(err, "failed to decode by unmarshaler") + return err } return nil } @@ -848,17 +923,27 @@ func (d *Decoder) decodeValue(ctx context.Context, dst reflect.Value, src ast.No } v := d.createDecodableValue(dst.Type()) if err := d.decodeValue(ctx, v, src); err != nil { - return errors.Wrapf(err, "failed to decode ptr value") + return err } - dst.Set(d.castToAssignableValue(v, dst.Type())) + castedValue, err := d.castToAssignableValue(v, dst.Type(), src) + if err != nil { + return err + } + dst.Set(castedValue) case reflect.Interface: if dst.Type() == astNodeType { dst.Set(reflect.ValueOf(src)) return nil } - v := reflect.ValueOf(d.nodeToValue(src)) + srcVal, err := d.nodeToValue(ctx, src) + if err != nil { + return err + } + v := reflect.ValueOf(srcVal) if v.IsValid() { dst.Set(v) + } else { + dst.Set(reflect.Zero(valueType)) } case reflect.Map: return d.decodeMap(ctx, dst, src) @@ -875,7 +960,10 @@ func (d *Decoder) decodeValue(ctx context.Context, dst reflect.Value, src ast.No } return d.decodeStruct(ctx, dst, src) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - v := d.nodeToValue(src) + v, err := d.nodeToValue(ctx, src) + if err != nil { + return err + } switch vv := v.(type) { case int64: if !dst.OverflowInt(vv) { @@ -899,14 +987,17 @@ func (d *Decoder) decodeValue(ctx context.Context, dst reflect.Value, src ast.No return nil } } else { // couldn't be parsed as float - return errTypeMismatch(valueType, reflect.TypeOf(v), src.GetToken()) + return errors.ErrTypeMismatch(valueType, reflect.TypeOf(v), src.GetToken()) } default: - return errTypeMismatch(valueType, reflect.TypeOf(v), src.GetToken()) + return errors.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) + v, err := d.nodeToValue(ctx, src) + if err != nil { + return err + } switch vv := v.(type) { case int64: if 0 <= vv && !dst.OverflowUint(uint64(vv)) { @@ -930,19 +1021,23 @@ func (d *Decoder) decodeValue(ctx context.Context, dst reflect.Value, src ast.No return nil } } else { // couldn't be parsed as float - return errTypeMismatch(valueType, reflect.TypeOf(v), src.GetToken()) + return errors.ErrTypeMismatch(valueType, reflect.TypeOf(v), src.GetToken()) } default: - return errTypeMismatch(valueType, reflect.TypeOf(v), src.GetToken()) + return errors.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)) + srcVal, err := d.nodeToValue(ctx, src) + if err != nil { + return err + } + v := reflect.ValueOf(srcVal) if v.IsValid() { convertedValue, err := d.convertValue(v, dst.Type(), src) if err != nil { - return errors.Wrapf(err, "failed to convert value") + return err } dst.Set(convertedValue) } @@ -960,23 +1055,29 @@ func (d *Decoder) createDecodableValue(typ reflect.Type) reflect.Value { return reflect.New(typ).Elem() } -func (d *Decoder) castToAssignableValue(value reflect.Value, target reflect.Type) reflect.Value { +func (d *Decoder) castToAssignableValue(value reflect.Value, target reflect.Type, src ast.Node) (reflect.Value, error) { if target.Kind() != reflect.Ptr { - return value - } - maxTryCount := 5 - tryCount := 0 - for { - if tryCount > maxTryCount { - return value + if !value.Type().AssignableTo(target) { + return reflect.Value{}, errors.ErrTypeMismatch(target, value.Type(), src.GetToken()) } + return value, nil + } + + const maxAddrCount = 5 + + for i := 0; i < maxAddrCount; i++ { if value.Type().AssignableTo(target) { break } + if !value.CanAddr() { + break + } value = value.Addr() - tryCount++ } - return value + if !value.Type().AssignableTo(target) { + return reflect.Value{}, errors.ErrTypeMismatch(target, value.Type(), src.GetToken()) + } + return value, nil } func (d *Decoder) createDecodedNewValue( @@ -984,61 +1085,79 @@ func (d *Decoder) createDecodedNewValue( ) (reflect.Value, error) { if node.Type() == ast.AliasType { aliasName := node.(*ast.AliasNode).Value.GetToken().Value - newValue := d.anchorValueMap[aliasName] - if newValue.IsValid() { - return newValue, nil + value := d.anchorValueMap[aliasName] + if value.IsValid() { + v, err := d.castToAssignableValue(value, typ, node) + if err == nil { + return v, nil + } + } + anchor, exists := d.anchorNodeMap[aliasName] + if exists { + node = anchor } } + 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 reflect.Value{}, err + } } - return newValue, nil + return d.castToAssignableValue(newValue, typ, node) } -func (d *Decoder) keyToNodeMap(node ast.Node, ignoreMergeKey bool, getKeyOrValueNode func(*ast.MapNodeIter) ast.Node) (map[string]ast.Node, error) { - mapNode, err := d.getMapNode(node) +func (d *Decoder) keyToNodeMap(ctx context.Context, node ast.Node, ignoreMergeKey bool, getKeyOrValueNode func(*ast.MapNodeIter) ast.Node) (map[string]ast.Node, error) { + d.stepIn() + defer d.stepOut() + if d.isExceededMaxDepth() { + return nil, ErrExceededMaxDepth + } + + mapNode, err := d.getMapNode(node, false) if err != nil { - return nil, errors.Wrapf(err, "failed to get map node") + return nil, err } keyMap := map[string]struct{}{} keyToNodeMap := map[string]ast.Node{} - if mapNode == nil { - return keyToNodeMap, nil - } mapIter := mapNode.MapRange() for mapIter.Next() { keyNode := mapIter.Key() - if keyNode.Type() == ast.MergeKeyType { + if keyNode.IsMergeKey() { if ignoreMergeKey { continue } - mergeMap, err := d.keyToNodeMap(mapIter.Value(), ignoreMergeKey, getKeyOrValueNode) + mergeMap, err := d.keyToNodeMap(ctx, mapIter.Value(), ignoreMergeKey, getKeyOrValueNode) if err != nil { - return nil, errors.Wrapf(err, "failed to get keyToNodeMap by MergeKey node") + return nil, err } for k, v := range mergeMap { if err := d.validateDuplicateKey(keyMap, k, v); err != nil { - return nil, errors.Wrapf(err, "invalid struct key") + return nil, err } keyToNodeMap[k] = v } } else { - key, ok := d.nodeToValue(keyNode).(string) + keyVal, err := d.nodeToValue(ctx, keyNode) + if err != nil { + return nil, err + } + key, ok := keyVal.(string) if !ok { - return nil, errors.Wrapf(err, "failed to decode map key") + return nil, err } if err := d.validateDuplicateKey(keyMap, key, keyNode); err != nil { - return nil, errors.Wrapf(err, "invalid struct key") + return nil, err } keyToNodeMap[key] = getKeyOrValueNode(mapIter) } @@ -1046,30 +1165,33 @@ func (d *Decoder) keyToNodeMap(node ast.Node, ignoreMergeKey bool, getKeyOrValue return keyToNodeMap, nil } -func (d *Decoder) keyToKeyNodeMap(node ast.Node, ignoreMergeKey bool) (map[string]ast.Node, error) { - m, err := d.keyToNodeMap(node, ignoreMergeKey, func(nodeMap *ast.MapNodeIter) ast.Node { return nodeMap.Key() }) +func (d *Decoder) keyToKeyNodeMap(ctx context.Context, node ast.Node, ignoreMergeKey bool) (map[string]ast.Node, error) { + m, err := d.keyToNodeMap(ctx, node, ignoreMergeKey, func(nodeMap *ast.MapNodeIter) ast.Node { return nodeMap.Key() }) if err != nil { - return nil, errors.Wrapf(err, "failed to get keyToNodeMap") + return nil, err } return m, nil } -func (d *Decoder) keyToValueNodeMap(node ast.Node, ignoreMergeKey bool) (map[string]ast.Node, error) { - m, err := d.keyToNodeMap(node, ignoreMergeKey, func(nodeMap *ast.MapNodeIter) ast.Node { return nodeMap.Value() }) +func (d *Decoder) keyToValueNodeMap(ctx context.Context, node ast.Node, ignoreMergeKey bool) (map[string]ast.Node, error) { + m, err := d.keyToNodeMap(ctx, node, ignoreMergeKey, func(nodeMap *ast.MapNodeIter) ast.Node { return nodeMap.Value() }) if err != nil { - return nil, errors.Wrapf(err, "failed to get keyToNodeMap") + return nil, err } return m, nil } func (d *Decoder) setDefaultValueIfConflicted(v reflect.Value, fieldMap StructFieldMap) error { + for v.Type().Kind() == reflect.Ptr { + v = v.Elem() + } typ := v.Type() if typ.Kind() != reflect.Struct { return nil } embeddedStructFieldMap, err := structFieldMap(typ) if err != nil { - return errors.Wrapf(err, "failed to get struct field map by embedded type") + return err } for i := 0; i < typ.NumField(); i++ { field := typ.Field(i) @@ -1098,17 +1220,20 @@ var allowedTimestampFormats = []string{ "2006-1-2", // date only } -func (d *Decoder) castToTime(src ast.Node) (time.Time, error) { +func (d *Decoder) castToTime(ctx context.Context, src ast.Node) (time.Time, error) { if src == nil { return time.Time{}, nil } - v := d.nodeToValue(src) + v, err := d.nodeToValue(ctx, src) + if err != nil { + return time.Time{}, err + } if t, ok := v.(time.Time); ok { return t, nil } s, ok := v.(string) if !ok { - return time.Time{}, errTypeMismatch(reflect.TypeOf(time.Time{}), reflect.TypeOf(v), src.GetToken()) + return time.Time{}, errors.ErrTypeMismatch(reflect.TypeOf(time.Time{}), reflect.TypeOf(v), src.GetToken()) } for _, format := range allowedTimestampFormats { t, err := time.Parse(format, s) @@ -1122,37 +1247,40 @@ func (d *Decoder) castToTime(src ast.Node) (time.Time, error) { } func (d *Decoder) decodeTime(ctx context.Context, dst reflect.Value, src ast.Node) error { - t, err := d.castToTime(src) + t, err := d.castToTime(ctx, src) if err != nil { - return errors.Wrapf(err, "failed to convert to time") + return err } dst.Set(reflect.ValueOf(t)) return nil } -func (d *Decoder) castToDuration(src ast.Node) (time.Duration, error) { +func (d *Decoder) castToDuration(ctx context.Context, src ast.Node) (time.Duration, error) { if src == nil { return 0, nil } - v := d.nodeToValue(src) + v, err := d.nodeToValue(ctx, src) + if err != nil { + return 0, err + } if t, ok := v.(time.Duration); ok { return t, nil } s, ok := v.(string) if !ok { - return 0, errTypeMismatch(reflect.TypeOf(time.Duration(0)), reflect.TypeOf(v), src.GetToken()) + return 0, errors.ErrTypeMismatch(reflect.TypeOf(time.Duration(0)), reflect.TypeOf(v), src.GetToken()) } t, err := time.ParseDuration(s) if err != nil { - return 0, errors.Wrapf(err, "failed to parse duration") + return 0, err } return t, nil } func (d *Decoder) decodeDuration(ctx context.Context, dst reflect.Value, src ast.Node) error { - t, err := d.castToDuration(src) + t, err := d.castToDuration(ctx, src) if err != nil { - return errors.Wrapf(err, "failed to convert to duration") + return err } dst.Set(reflect.ValueOf(t)) return nil @@ -1160,18 +1288,15 @@ func (d *Decoder) decodeDuration(ctx context.Context, dst reflect.Value, src ast // getMergeAliasName support single alias only func (d *Decoder) getMergeAliasName(src ast.Node) string { - mapNode, err := d.getMapNode(src) + mapNode, err := d.getMapNode(src, true) if err != nil { return "" } - if mapNode == nil { - return "" - } mapIter := mapNode.MapRange() for mapIter.Next() { key := mapIter.Key() value := mapIter.Value() - if key.Type() == ast.MergeKeyType && value.Type() == ast.AliasType { + if key.IsMergeKey() && value.Type() == ast.AliasType { return value.(*ast.AliasNode).Value.GetToken().Value } } @@ -1182,6 +1307,12 @@ func (d *Decoder) decodeStruct(ctx context.Context, dst reflect.Value, src ast.N if src == nil { return nil } + d.stepIn() + defer d.stepOut() + if d.isExceededMaxDepth() { + return ErrExceededMaxDepth + } + structType := dst.Type() srcValue := reflect.ValueOf(src) srcType := srcValue.Type() @@ -1196,18 +1327,18 @@ func (d *Decoder) decodeStruct(ctx context.Context, dst reflect.Value, src ast.N } structFieldMap, err := structFieldMap(structType) if err != nil { - return errors.Wrapf(err, "failed to create struct field map") + return err } ignoreMergeKey := structFieldMap.hasMergeProperty() - keyToNodeMap, err := d.keyToValueNodeMap(src, ignoreMergeKey) + keyToNodeMap, err := d.keyToValueNodeMap(ctx, src, ignoreMergeKey) if err != nil { - return errors.Wrapf(err, "failed to get keyToValueNodeMap") + return err } var unknownFields map[string]ast.Node if d.disallowUnknownField { - unknownFields, err = d.keyToKeyNodeMap(src, ignoreMergeKey) + unknownFields, err = d.keyToKeyNodeMap(ctx, src, ignoreMergeKey) if err != nil { - return errors.Wrapf(err, "failed to get keyToKeyNodeMap") + return err } } @@ -1226,13 +1357,17 @@ func (d *Decoder) decodeStruct(ctx context.Context, dst reflect.Value, src ast.N if aliasName != "" { newFieldValue := d.anchorValueMap[aliasName] if newFieldValue.IsValid() { - fieldValue.Set(d.castToAssignableValue(newFieldValue, fieldValue.Type())) + value, err := d.castToAssignableValue(newFieldValue, fieldValue.Type(), d.anchorNodeMap[aliasName]) + if err != nil { + return err + } + fieldValue.Set(value) } } continue } if !fieldValue.CanSet() { - return xerrors.Errorf("cannot set embedded type as unexported field %s.%s", field.PkgPath, field.Name) + return fmt.Errorf("cannot set embedded type as unexported field %s.%s", field.PkgPath, field.Name) } if fieldValue.Type().Kind() == reflect.Ptr && src.Type() == ast.NullType { // set nil value to pointer @@ -1247,7 +1382,7 @@ func (d *Decoder) decodeStruct(ctx context.Context, dst reflect.Value, src ast.N newFieldValue, err := d.createDecodedNewValue(ctx, fieldValue.Type(), fieldValue, mapNode) if d.disallowUnknownField { if err := d.deleteStructKeys(fieldValue.Type(), unknownFields); err != nil { - return errors.Wrapf(err, "cannot delete struct keys") + return err } } @@ -1256,7 +1391,7 @@ func (d *Decoder) decodeStruct(ctx context.Context, dst reflect.Value, src ast.N continue } var te *errors.TypeError - if xerrors.As(err, &te) { + if errors.As(err, &te) { if te.StructFieldName != nil { fieldName := fmt.Sprintf("%s.%s", structType.Name(), *te.StructFieldName) te.StructFieldName = &fieldName @@ -1271,8 +1406,8 @@ func (d *Decoder) decodeStruct(ctx context.Context, dst reflect.Value, src ast.N } continue } - d.setDefaultValueIfConflicted(newFieldValue, structFieldMap) - fieldValue.Set(d.castToAssignableValue(newFieldValue, fieldValue.Type())) + _ = d.setDefaultValueIfConflicted(newFieldValue, structFieldMap) + fieldValue.Set(newFieldValue) continue } v, exists := keyToNodeMap[structField.RenderName] @@ -1292,7 +1427,7 @@ func (d *Decoder) decodeStruct(ctx context.Context, dst reflect.Value, src ast.N continue } var te *errors.TypeError - if xerrors.As(err, &te) { + if errors.As(err, &te) { fieldName := fmt.Sprintf("%s.%s", structType.Name(), field.Name) te.StructFieldName = &fieldName foundErr = te @@ -1301,22 +1436,22 @@ func (d *Decoder) decodeStruct(ctx context.Context, dst reflect.Value, src ast.N } continue } - fieldValue.Set(d.castToAssignableValue(newFieldValue, fieldValue.Type())) + fieldValue.Set(newFieldValue) } if foundErr != nil { - return errors.Wrapf(foundErr, "failed to decode value") + return foundErr } // Ignore unknown fields when parsing an inline struct (recognized by a nil token). // Unknown fields are expected (they could be fields from the parent struct). if len(unknownFields) != 0 && d.disallowUnknownField && src.GetToken() != nil { for key, node := range unknownFields { - return errUnknownField(fmt.Sprintf(`unknown field "%s"`, key), node.GetToken()) + return errors.ErrUnknownField(fmt.Sprintf(`unknown field "%s"`, key), node.GetToken()) } } if d.validator != nil { - if err := d.validator.Struct(dst.Interface()); err != nil { + if err := d.validator.Struct(dst.Addr().Interface()); err != nil { ev := reflect.ValueOf(err) if ev.Type().Kind() == reflect.Slice { for i := 0; i < ev.Len(); i++ { @@ -1332,7 +1467,10 @@ func (d *Decoder) decodeStruct(ctx context.Context, dst reflect.Value, src ast.N node, exists := keyToNodeMap[structField.RenderName] if exists { // TODO: to make FieldError message cutomizable - return errors.ErrSyntax(fmt.Sprintf("%s", err), node.GetToken()) + return errors.ErrSyntax( + fmt.Sprintf("%s", err), + d.getParentMapTokenIfExistsForValidationError(node.Type(), node.GetToken()), + ) } else if t := src.GetToken(); t != nil && t.Prev != nil && t.Prev.Prev != nil { // A missing required field will not be in the keyToNodeMap // the error needs to be associated with the parent of the source node @@ -1346,10 +1484,47 @@ func (d *Decoder) decodeStruct(ctx context.Context, dst reflect.Value, src ast.N return nil } +// getParentMapTokenIfExists if the NodeType is a container type such as MappingType or SequenceType, +// it is necessary to return the parent MapNode's colon token to represent the entire container. +func (d *Decoder) getParentMapTokenIfExistsForValidationError(typ ast.NodeType, tk *token.Token) *token.Token { + if tk == nil { + return nil + } + if typ == ast.MappingType { + // map: + // key: value + // ^ current token ( colon ) + if tk.Prev == nil { + return tk + } + key := tk.Prev + if key.Prev == nil { + return tk + } + return key.Prev + } + if typ == ast.SequenceType { + // map: + // - value + // ^ current token ( sequence entry ) + if tk.Prev == nil { + return tk + } + return tk.Prev + } + return tk +} + func (d *Decoder) decodeArray(ctx context.Context, dst reflect.Value, src ast.Node) error { + d.stepIn() + defer d.stepOut() + if d.isExceededMaxDepth() { + return ErrExceededMaxDepth + } + arrayNode, err := d.getArrayNode(src) if err != nil { - return errors.Wrapf(err, "failed to get array node") + return err } if arrayNode == nil { return nil @@ -1373,23 +1548,28 @@ func (d *Decoder) decodeArray(ctx context.Context, dst reflect.Value, src ast.No foundErr = err } continue - } else { - arrayValue.Index(idx).Set(d.castToAssignableValue(dstValue, elemType)) } + arrayValue.Index(idx).Set(dstValue) } idx++ } dst.Set(arrayValue) if foundErr != nil { - return errors.Wrapf(foundErr, "failed to decode value") + return foundErr } return nil } func (d *Decoder) decodeSlice(ctx context.Context, dst reflect.Value, src ast.Node) error { + d.stepIn() + defer d.stepOut() + if d.isExceededMaxDepth() { + return ErrExceededMaxDepth + } + arrayNode, err := d.getArrayNode(src) if err != nil { - return errors.Wrapf(err, "failed to get array node") + return err } if arrayNode == nil { return nil @@ -1414,22 +1594,25 @@ func (d *Decoder) decodeSlice(ctx context.Context, dst reflect.Value, src ast.No } continue } - sliceValue = reflect.Append(sliceValue, d.castToAssignableValue(dstValue, elemType)) + sliceValue = reflect.Append(sliceValue, dstValue) } dst.Set(sliceValue) if foundErr != nil { - return errors.Wrapf(foundErr, "failed to decode value") + return foundErr } return nil } func (d *Decoder) decodeMapItem(ctx context.Context, dst *MapItem, src ast.Node) error { - mapNode, err := d.getMapNode(src) - if err != nil { - return errors.Wrapf(err, "failed to get map node") + d.stepIn() + defer d.stepOut() + if d.isExceededMaxDepth() { + return ErrExceededMaxDepth } - if mapNode == nil { - return nil + + mapNode, err := d.getMapNode(src, isMerge(ctx)) + if err != nil { + return err } mapIter := mapNode.MapRange() if !mapIter.Next() { @@ -1437,16 +1620,21 @@ func (d *Decoder) decodeMapItem(ctx context.Context, dst *MapItem, src ast.Node) } key := mapIter.Key() value := mapIter.Value() - if key.Type() == ast.MergeKeyType { - if err := d.decodeMapItem(ctx, dst, value); err != nil { - return errors.Wrapf(err, "failed to decode map with merge key") + if key.IsMergeKey() { + if err := d.decodeMapItem(withMerge(ctx), dst, value); err != nil { + return err } return nil } - *dst = MapItem{ - Key: d.nodeToValue(key), - Value: d.nodeToValue(value), + k, err := d.nodeToValue(ctx, key) + if err != nil { + return err } + v, err := d.nodeToValue(ctx, value) + if err != nil { + return err + } + *dst = MapItem{Key: k, Value: v} return nil } @@ -1455,9 +1643,9 @@ func (d *Decoder) validateDuplicateKey(keyMap map[string]struct{}, key interface if !ok { return nil } - if d.disallowDuplicateKey { + if !d.allowDuplicateMapKey { if _, exists := keyMap[k]; exists { - return errDuplicateKey(fmt.Sprintf(`duplicate key "%s"`, k), keyNode.GetToken()) + return errors.ErrDuplicateKey(fmt.Sprintf(`duplicate key "%s"`, k), keyNode.GetToken()) } } keyMap[k] = struct{}{} @@ -1465,12 +1653,15 @@ func (d *Decoder) validateDuplicateKey(keyMap map[string]struct{}, key interface } func (d *Decoder) decodeMapSlice(ctx context.Context, dst *MapSlice, src ast.Node) error { - mapNode, err := d.getMapNode(src) - if err != nil { - return errors.Wrapf(err, "failed to get map node") + d.stepIn() + defer d.stepOut() + if d.isExceededMaxDepth() { + return ErrExceededMaxDepth } - if mapNode == nil { - return nil + + mapNode, err := d.getMapNode(src, isMerge(ctx)) + if err != nil { + return err } mapSlice := MapSlice{} mapIter := mapNode.MapRange() @@ -1478,39 +1669,46 @@ func (d *Decoder) decodeMapSlice(ctx context.Context, dst *MapSlice, src ast.Nod for mapIter.Next() { key := mapIter.Key() value := mapIter.Value() - if key.Type() == ast.MergeKeyType { + if key.IsMergeKey() { var m MapSlice - if err := d.decodeMapSlice(ctx, &m, value); err != nil { - return errors.Wrapf(err, "failed to decode map with merge key") + if err := d.decodeMapSlice(withMerge(ctx), &m, value); err != nil { + return err } for _, v := range m { if err := d.validateDuplicateKey(keyMap, v.Key, value); err != nil { - return errors.Wrapf(err, "invalid map key") + return err } mapSlice = append(mapSlice, v) } continue } - k := d.nodeToValue(key) - if err := d.validateDuplicateKey(keyMap, k, key); err != nil { - return errors.Wrapf(err, "invalid map key") + k, err := d.nodeToValue(ctx, key) + if err != nil { + return err } - mapSlice = append(mapSlice, MapItem{ - Key: k, - Value: d.nodeToValue(value), - }) + if err := d.validateDuplicateKey(keyMap, k, key); err != nil { + return err + } + v, err := d.nodeToValue(ctx, value) + if err != nil { + return err + } + mapSlice = append(mapSlice, MapItem{Key: k, Value: v}) } *dst = mapSlice return nil } func (d *Decoder) decodeMap(ctx context.Context, dst reflect.Value, src ast.Node) error { - mapNode, err := d.getMapNode(src) - if err != nil { - return errors.Wrapf(err, "failed to get map node") + d.stepIn() + defer d.stepOut() + if d.isExceededMaxDepth() { + return ErrExceededMaxDepth } - if mapNode == nil { - return nil + + mapNode, err := d.getMapNode(src, isMerge(ctx)) + if err != nil { + return err } mapType := dst.Type() mapValue := reflect.MakeMap(mapType) @@ -1522,14 +1720,14 @@ func (d *Decoder) decodeMap(ctx context.Context, dst reflect.Value, src ast.Node for mapIter.Next() { key := mapIter.Key() value := mapIter.Value() - if key.Type() == ast.MergeKeyType { - if err := d.decodeMap(ctx, dst, value); err != nil { - return errors.Wrapf(err, "failed to decode map with merge key") + if key.IsMergeKey() { + if err := d.decodeMap(withMerge(ctx), dst, value); err != nil { + return err } iter := dst.MapRange() for iter.Next() { if err := d.validateDuplicateKey(keyMap, iter.Key(), value); err != nil { - return errors.Wrapf(err, "invalid map key") + return err } mapValue.SetMapIndex(iter.Key(), iter.Value()) } @@ -1539,10 +1737,14 @@ func (d *Decoder) decodeMap(ctx context.Context, dst reflect.Value, src ast.Node k := d.createDecodableValue(keyType) if d.canDecodeByUnmarshaler(k) { if err := d.decodeByUnmarshaler(ctx, k, key); err != nil { - return errors.Wrapf(err, "failed to decode by unmarshaler") + return err } } else { - k = reflect.ValueOf(d.nodeToValue(key)) + keyVal, err := d.nodeToValue(ctx, key) + if err != nil { + return err + } + k = reflect.ValueOf(keyVal) if k.IsValid() && k.Type().ConvertibleTo(keyType) { k = k.Convert(keyType) } @@ -1550,7 +1752,7 @@ func (d *Decoder) decodeMap(ctx context.Context, dst reflect.Value, src ast.Node if k.IsValid() { if err := d.validateDuplicateKey(keyMap, k.Interface(), key); err != nil { - return errors.Wrapf(err, "invalid map key") + return err } } if valueType.Kind() == reflect.Ptr && value.Type() == ast.NullType { @@ -1566,14 +1768,20 @@ func (d *Decoder) decodeMap(ctx context.Context, dst reflect.Value, src ast.Node } if !k.IsValid() { // expect nil key - mapValue.SetMapIndex(d.createDecodableValue(keyType), d.castToAssignableValue(dstValue, valueType)) + mapValue.SetMapIndex(d.createDecodableValue(keyType), dstValue) continue } - mapValue.SetMapIndex(k, d.castToAssignableValue(dstValue, valueType)) + if keyType.Kind() != k.Kind() { + return errors.ErrSyntax( + fmt.Sprintf("cannot convert %q type to %q type", k.Kind(), keyType.Kind()), + key.GetToken(), + ) + } + mapValue.SetMapIndex(k, dstValue) } dst.Set(mapValue) if foundErr != nil { - return errors.Wrapf(foundErr, "failed to decode value") + return foundErr } return nil } @@ -1581,7 +1789,7 @@ func (d *Decoder) decodeMap(ctx context.Context, dst reflect.Value, src ast.Node func (d *Decoder) fileToReader(file string) (io.Reader, error) { reader, err := os.Open(file) if err != nil { - return nil, errors.Wrapf(err, "failed to open file") + return nil, err } return reader, nil } @@ -1601,7 +1809,7 @@ func (d *Decoder) readersUnderDir(dir string) ([]io.Reader, error) { pattern := fmt.Sprintf("%s/*", dir) matches, err := filepath.Glob(pattern) if err != nil { - return nil, errors.Wrapf(err, "failed to get files by %s", pattern) + return nil, err } readers := []io.Reader{} for _, match := range matches { @@ -1610,7 +1818,7 @@ func (d *Decoder) readersUnderDir(dir string) ([]io.Reader, error) { } reader, err := d.fileToReader(match) if err != nil { - return nil, errors.Wrapf(err, "failed to get reader") + return nil, err } readers = append(readers, reader) } @@ -1619,32 +1827,32 @@ 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 readerErr } readers = append(readers, reader) return nil }); err != nil { - return nil, errors.Wrapf(err, "interrupt walk in %s", dir) + return nil, err } return readers, nil } -func (d *Decoder) resolveReference() error { +func (d *Decoder) resolveReference(ctx context.Context) error { for _, opt := range d.opts { if err := opt(d); err != nil { - return errors.Wrapf(err, "failed to exec option") + return err } } for _, file := range d.referenceFiles { reader, err := d.fileToReader(file) if err != nil { - return errors.Wrapf(err, "failed to get reader") + return err } d.referenceReaders = append(d.referenceReaders, reader) } @@ -1652,13 +1860,13 @@ func (d *Decoder) resolveReference() error { if !d.isRecursiveDir { readers, err := d.readersUnderDir(dir) if err != nil { - return errors.Wrapf(err, "failed to get readers from under the %s", dir) + return err } d.referenceReaders = append(d.referenceReaders, readers...) } else { readers, err := d.readersUnderDirRecursive(dir) if err != nil { - return errors.Wrapf(err, "failed to get readers from under the %s", dir) + return err } d.referenceReaders = append(d.referenceReaders, readers...) } @@ -1666,32 +1874,46 @@ func (d *Decoder) resolveReference() error { for _, reader := range d.referenceReaders { bytes, err := io.ReadAll(reader) if err != nil { - return errors.Wrapf(err, "failed to read buffer") + return err } // assign new anchor definition to anchorMap - if _, err := d.parse(bytes); err != nil { - return errors.Wrapf(err, "failed to decode") + if _, err := d.parse(ctx, bytes); err != nil { + return err } } d.isResolvedReference = true return nil } -func (d *Decoder) parse(bytes []byte) (*ast.File, error) { +func (d *Decoder) parse(ctx context.Context, bytes []byte) (*ast.File, error) { var parseMode parser.Mode if d.toCommentMap != nil { parseMode = parser.ParseComments } - f, err := parser.ParseBytes(bytes, parseMode) + var opts []parser.Option + if d.allowDuplicateMapKey { + opts = append(opts, parser.AllowDuplicateMapKey()) + } + f, err := parser.ParseBytes(bytes, parseMode, opts...) if err != nil { - return nil, errors.Wrapf(err, "failed to parse yaml") + return nil, err } normalizedFile := &ast.File{} for _, doc := range f.Docs { // try to decode ast.Node to value and map anchor value to anchorMap - if v := d.nodeToValue(doc.Body); v != nil { + v, err := d.nodeToValue(ctx, doc.Body) + if err != nil { + return nil, err + } + if v != nil || (doc.Body != nil && doc.Body.Type() == ast.NullType) { normalizedFile.Docs = append(normalizedFile.Docs, doc) + cm := CommentMap{} + maps.Copy(cm, d.toCommentMap) + d.commentMaps = append(d.commentMaps, cm) + } + for k := range d.toCommentMap { + delete(d.toCommentMap, k) } } return normalizedFile, nil @@ -1701,25 +1923,34 @@ func (d *Decoder) isInitialized() bool { return d.parsedFile != nil } -func (d *Decoder) decodeInit() error { +func (d *Decoder) decodeInit(ctx context.Context) error { if !d.isResolvedReference { - if err := d.resolveReference(); err != nil { - return errors.Wrapf(err, "failed to resolve reference") + if err := d.resolveReference(ctx); err != nil { + return err } } var buf bytes.Buffer if _, err := io.Copy(&buf, d.reader); err != nil { - return errors.Wrapf(err, "failed to copy from reader") + return err } - file, err := d.parse(buf.Bytes()) + file, err := d.parse(ctx, buf.Bytes()) if err != nil { - return errors.Wrapf(err, "failed to decode") + return err } d.parsedFile = file return nil } func (d *Decoder) decode(ctx context.Context, v reflect.Value) error { + d.decodeDepth = 0 + d.anchorValueMap = make(map[string]reflect.Value) + if len(d.parsedFile.Docs) == 0 { + // empty document. + dst := v.Elem() + if dst.IsValid() { + dst.Set(reflect.Zero(dst.Type())) + } + } if len(d.parsedFile.Docs) <= d.streamIndex { return io.EOF } @@ -1727,8 +1958,11 @@ func (d *Decoder) decode(ctx context.Context, v reflect.Value) error { if body == nil { return nil } + if len(d.commentMaps) > d.streamIndex { + maps.Copy(d.toCommentMap, d.commentMaps[d.streamIndex]) + } if err := d.decodeValue(ctx, v.Elem(), body); err != nil { - return errors.Wrapf(err, "failed to decode value") + return err } d.streamIndex++ return nil @@ -1747,26 +1981,20 @@ func (d *Decoder) Decode(v interface{}) error { // and stores it in the value pointed to by v with context.Context. func (d *Decoder) DecodeContext(ctx context.Context, v interface{}) error { rv := reflect.ValueOf(v) - if rv.Type().Kind() != reflect.Ptr { - return errors.ErrDecodeRequiredPointerType + if !rv.IsValid() || rv.Type().Kind() != reflect.Ptr { + return ErrDecodeRequiredPointerType } if d.isInitialized() { if err := d.decode(ctx, rv); err != nil { - if err == io.EOF { - return err - } - return errors.Wrapf(err, "failed to decode") + return err } return nil } - if err := d.decodeInit(); err != nil { - return errors.Wrapf(err, "failed to decodeInit") + if err := d.decodeInit(ctx); err != nil { + return err } if err := d.decode(ctx, rv); err != nil { - if err == io.EOF { - return err - } - return errors.Wrapf(err, "failed to decode") + return err } return nil } @@ -1780,17 +2008,19 @@ func (d *Decoder) DecodeFromNode(node ast.Node, v interface{}) error { func (d *Decoder) DecodeFromNodeContext(ctx context.Context, node ast.Node, v interface{}) error { rv := reflect.ValueOf(v) if rv.Type().Kind() != reflect.Ptr { - return errors.ErrDecodeRequiredPointerType + return ErrDecodeRequiredPointerType } if !d.isInitialized() { - if err := d.decodeInit(); err != nil { - return errors.Wrapf(err, "failed to decodInit") + if err := d.decodeInit(ctx); err != nil { + return err } } // resolve references to the anchor on the same file - d.nodeToValue(node) + if _, err := d.nodeToValue(ctx, node); err != nil { + return err + } if err := d.decodeValue(ctx, rv.Elem(), node); err != nil { - return errors.Wrapf(err, "failed to decode value") + return err } return nil } diff --git a/vendor/github.com/goccy/go-yaml/encode.go b/vendor/github.com/goccy/go-yaml/encode.go index 3b9b29814..a686f28a9 100644 --- a/vendor/github.com/goccy/go-yaml/encode.go +++ b/vendor/github.com/goccy/go-yaml/encode.go @@ -17,7 +17,6 @@ import ( "github.com/goccy/go-yaml/parser" "github.com/goccy/go-yaml/printer" "github.com/goccy/go-yaml/token" - "golang.org/x/xerrors" ) const ( @@ -29,24 +28,29 @@ const ( type Encoder struct { writer io.Writer opts []EncodeOption - indent int - indentSequence bool singleQuote bool isFlowStyle bool isJSONStyle bool useJSONMarshaler bool + enableSmartAnchor bool + aliasRefToName map[uintptr]string + anchorRefToName map[uintptr]string + anchorNameMap map[string]struct{} anchorCallback func(*ast.AnchorNode, interface{}) error - anchorPtrToNameMap map[uintptr]string - customMarshalerMap map[reflect.Type]func(interface{}) ([]byte, error) + customMarshalerMap map[reflect.Type]func(context.Context, interface{}) ([]byte, error) + omitZero bool + omitEmpty bool + autoInt bool useLiteralStyleIfMultiline bool commentMap map[*Path][]*Comment written bool - line int - column int - offset int - indentNum int - indentLevel int + line int + column int + offset int + indentNum int + indentLevel int + indentSequence bool } // NewEncoder returns a new encoder that writes to w. @@ -55,12 +59,14 @@ func NewEncoder(w io.Writer, opts ...EncodeOption) *Encoder { return &Encoder{ writer: w, opts: opts, - indent: DefaultIndentSpaces, - anchorPtrToNameMap: map[uintptr]string{}, - customMarshalerMap: map[reflect.Type]func(interface{}) ([]byte, error){}, + customMarshalerMap: map[reflect.Type]func(context.Context, interface{}) ([]byte, error){}, line: 1, column: 1, offset: 0, + indentNum: DefaultIndentSpaces, + anchorRefToName: make(map[uintptr]string), + anchorNameMap: make(map[string]struct{}), + aliasRefToName: make(map[uintptr]string), } } @@ -84,19 +90,19 @@ func (e *Encoder) Encode(v interface{}) error { func (e *Encoder) EncodeContext(ctx context.Context, v interface{}) error { node, err := e.EncodeToNodeContext(ctx, v) if err != nil { - return errors.Wrapf(err, "failed to encode to node") + return err } if err := e.setCommentByCommentMap(node); err != nil { - return errors.Wrapf(err, "failed to set comment by comment map") + return err } if !e.written { 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 } @@ -109,12 +115,19 @@ func (e *Encoder) EncodeToNode(v interface{}) (ast.Node, error) { func (e *Encoder) EncodeToNodeContext(ctx context.Context, v interface{}) (ast.Node, error) { for _, opt := range e.opts { if err := opt(e); err != nil { - return nil, errors.Wrapf(err, "failed to run option for encoder") + return nil, err } } + if e.enableSmartAnchor { + // during the first encoding, store all mappings between alias addresses and their names. + if _, err := e.encodeValue(ctx, reflect.ValueOf(v), 1); err != nil { + return nil, err + } + e.clearSmartAnchorRef() + } node, err := e.encodeValue(ctx, reflect.ValueOf(v), 1) if err != nil { - return nil, errors.Wrapf(err, "failed to encode value") + return nil, err } return node, nil } @@ -126,7 +139,7 @@ func (e *Encoder) setCommentByCommentMap(node ast.Node) error { for path, comments := range e.commentMap { n, err := path.FilterNode(node) if err != nil { - return errors.Wrapf(err, "failed to filter node") + return err } if n == nil { continue @@ -140,15 +153,15 @@ func (e *Encoder) setCommentByCommentMap(node ast.Node) error { switch comment.Position { case CommentHeadPosition: if err := e.setHeadComment(node, n, commentGroup); err != nil { - return errors.Wrapf(err, "failed to set head comment") + return err } case CommentLinePosition: if err := e.setLineComment(node, n, commentGroup); err != nil { - return errors.Wrapf(err, "failed to set line comment") + return err } case CommentFootPosition: if err := e.setFootComment(node, n, commentGroup); err != nil { - return errors.Wrapf(err, "failed to set foot comment") + return err } default: return ErrUnknownCommentPositionType @@ -166,11 +179,11 @@ func (e *Encoder) setHeadComment(node ast.Node, filtered ast.Node, comment *ast. switch p := parent.(type) { case *ast.MappingValueNode: if err := p.SetComment(comment); err != nil { - return errors.Wrapf(err, "failed to set comment") + return err } case *ast.MappingNode: if err := p.SetComment(comment); err != nil { - return errors.Wrapf(err, "failed to set comment") + return err } case *ast.SequenceNode: if len(p.ValueHeadComments) == 0 { @@ -196,11 +209,11 @@ func (e *Encoder) setLineComment(node ast.Node, filtered ast.Node, comment *ast. // Line comment cannot be set for mapping value node. // It should probably be set for the parent map node if err := e.setLineCommentToParentMapNode(node, filtered, comment); err != nil { - return errors.Wrapf(err, "failed to set line comment to parent node") + return err } default: if err := filtered.SetComment(comment); err != nil { - return errors.Wrapf(err, "failed to set comment") + return err } } return nil @@ -214,11 +227,11 @@ func (e *Encoder) setLineCommentToParentMapNode(node ast.Node, filtered ast.Node switch p := parent.(type) { case *ast.MappingValueNode: if err := p.Key.SetComment(comment); err != nil { - return errors.Wrapf(err, "failed to set comment") + return err } case *ast.MappingNode: if err := p.SetComment(comment); err != nil { - return errors.Wrapf(err, "failed to set comment") + return err } default: return ErrUnsupportedLinePositionType(parent) @@ -247,7 +260,7 @@ func (e *Encoder) setFootComment(node ast.Node, filtered ast.Node, comment *ast. func (e *Encoder) encodeDocument(doc []byte) (ast.Node, error) { f, err := parser.ParseBytes(doc, 0) if err != nil { - return nil, errors.Wrapf(err, "failed to parse yaml") + return nil, err } for _, docNode := range f.Docs { if docNode.Body != nil { @@ -288,7 +301,7 @@ func (e *Encoder) existsTypeInCustomMarshalerMap(t reflect.Type) bool { return false } -func (e *Encoder) marshalerFromCustomMarshalerMap(t reflect.Type) (func(interface{}) ([]byte, error), bool) { +func (e *Encoder) marshalerFromCustomMarshalerMap(t reflect.Type) (func(context.Context, interface{}) ([]byte, error), bool) { if marshaler, exists := e.customMarshalerMap[t]; exists { return marshaler, exists } @@ -318,7 +331,7 @@ func (e *Encoder) canEncodeByMarshaler(v reflect.Value) bool { return true case InterfaceMarshaler: return true - case time.Time: + case time.Time, *time.Time: return true case time.Duration: return true @@ -334,13 +347,13 @@ func (e *Encoder) encodeByMarshaler(ctx context.Context, v reflect.Value, column iface := v.Interface() if marshaler, exists := e.marshalerFromCustomMarshalerMap(v.Type()); exists { - doc, err := marshaler(iface) + doc, err := marshaler(ctx, iface) if err != nil { - return nil, errors.Wrapf(err, "failed to MarshalYAML") + return nil, err } node, err := e.encodeDocument(doc) if err != nil { - return nil, errors.Wrapf(err, "failed to encode document") + return nil, err } return node, nil } @@ -348,11 +361,11 @@ func (e *Encoder) encodeByMarshaler(ctx context.Context, v reflect.Value, column if marshaler, ok := iface.(BytesMarshalerContext); ok { doc, err := marshaler.MarshalYAML(ctx) if err != nil { - return nil, errors.Wrapf(err, "failed to MarshalYAML") + return nil, err } node, err := e.encodeDocument(doc) if err != nil { - return nil, errors.Wrapf(err, "failed to encode document") + return nil, err } return node, nil } @@ -360,11 +373,11 @@ func (e *Encoder) encodeByMarshaler(ctx context.Context, v reflect.Value, column if marshaler, ok := iface.(BytesMarshaler); ok { doc, err := marshaler.MarshalYAML() if err != nil { - return nil, errors.Wrapf(err, "failed to MarshalYAML") + return nil, err } node, err := e.encodeDocument(doc) if err != nil { - return nil, errors.Wrapf(err, "failed to encode document") + return nil, err } return node, nil } @@ -372,7 +385,7 @@ func (e *Encoder) encodeByMarshaler(ctx context.Context, v reflect.Value, column if marshaler, ok := iface.(InterfaceMarshalerContext); ok { marshalV, err := marshaler.MarshalYAML(ctx) if err != nil { - return nil, errors.Wrapf(err, "failed to MarshalYAML") + return nil, err } return e.encodeValue(ctx, reflect.ValueOf(marshalV), column) } @@ -380,7 +393,7 @@ func (e *Encoder) encodeByMarshaler(ctx context.Context, v reflect.Value, column if marshaler, ok := iface.(InterfaceMarshaler); ok { marshalV, err := marshaler.MarshalYAML() if err != nil { - return nil, errors.Wrapf(err, "failed to MarshalYAML") + return nil, err } return e.encodeValue(ctx, reflect.ValueOf(marshalV), column) } @@ -388,20 +401,21 @@ func (e *Encoder) encodeByMarshaler(ctx context.Context, v reflect.Value, column if t, ok := iface.(time.Time); ok { return e.encodeTime(t, column), nil } + // Handle *time.Time explicitly since it implements TextMarshaler and shouldn't be treated as plain text + if t, ok := iface.(*time.Time); ok && t != nil { + return e.encodeTime(*t, column), nil + } if t, ok := iface.(time.Duration); ok { return e.encodeDuration(t, column), nil } if marshaler, ok := iface.(encoding.TextMarshaler); ok { - doc, err := marshaler.MarshalText() + text, err := marshaler.MarshalText() if err != nil { - return nil, errors.Wrapf(err, "failed to MarshalText") - } - node, err := e.encodeDocument(doc) - if err != nil { - return nil, errors.Wrapf(err, "failed to encode document") + return nil, err } + node := e.encodeString(string(text), column) return node, nil } @@ -409,21 +423,21 @@ func (e *Encoder) encodeByMarshaler(ctx context.Context, v reflect.Value, column if marshaler, ok := iface.(jsonMarshaler); ok { jsonBytes, err := marshaler.MarshalJSON() if err != nil { - return nil, errors.Wrapf(err, "failed to MarshalJSON") + return nil, err } doc, err := JSONToYAML(jsonBytes) if err != nil { - return nil, errors.Wrapf(err, "failed to convert json to yaml") + return nil, err } node, err := e.encodeDocument(doc) if err != nil { - return nil, errors.Wrapf(err, "failed to encode document") + return nil, err } return node, nil } } - return nil, xerrors.Errorf("does not implemented Marshaler") + return nil, errors.New("does not implemented Marshaler") } func (e *Encoder) encodeValue(ctx context.Context, v reflect.Value, column int) (ast.Node, error) { @@ -433,7 +447,7 @@ func (e *Encoder) encodeValue(ctx context.Context, v reflect.Value, column int) if e.canEncodeByMarshaler(v) { node, err := e.encodeByMarshaler(ctx, v, column) if err != nil { - return nil, errors.Wrapf(err, "failed to encode by marshaler") + return nil, err } return node, nil } @@ -447,12 +461,8 @@ func (e *Encoder) encodeValue(ctx context.Context, v reflect.Value, column int) case reflect.Float64: return e.encodeFloat(v.Float(), 64), nil case reflect.Ptr: - anchorName := e.anchorPtrToNameMap[v.Pointer()] - if anchorName != "" { - aliasName := anchorName - alias := ast.Alias(token.New("*", "*", e.pos(column))) - alias.Value = ast.String(token.New(aliasName, aliasName, e.pos(column))) - return alias, nil + if value := e.encodePtrAnchor(v, column); value != nil { + return value, nil } return e.encodeValue(ctx, v.Elem(), column) case reflect.Interface: @@ -465,6 +475,9 @@ func (e *Encoder) encodeValue(ctx context.Context, v reflect.Value, column int) if mapSlice, ok := v.Interface().(MapSlice); ok { return e.encodeMapSlice(ctx, mapSlice, column) } + if value := e.encodePtrAnchor(v, column); value != nil { + return value, nil + } return e.encodeSlice(ctx, v) case reflect.Array: return e.encodeArray(ctx, v) @@ -479,12 +492,27 @@ func (e *Encoder) encodeValue(ctx context.Context, v reflect.Value, column int) } return e.encodeStruct(ctx, v, column) case reflect.Map: - return e.encodeMap(ctx, v, column), nil + if value := e.encodePtrAnchor(v, column); value != nil { + return value, nil + } + return e.encodeMap(ctx, v, column) default: - return nil, xerrors.Errorf("unknown value type %s", v.Type().String()) + return nil, fmt.Errorf("unknown value type %s", v.Type().String()) } } +func (e *Encoder) encodePtrAnchor(v reflect.Value, column int) ast.Node { + anchorName, exists := e.getAnchor(v.Pointer()) + if !exists { + return nil + } + aliasName := anchorName + alias := ast.Alias(token.New("*", "*", e.pos(column))) + alias.Value = ast.String(token.New(aliasName, aliasName, e.pos(column))) + e.setSmartAlias(aliasName, v.Pointer()) + return alias +} + func (e *Encoder) pos(column int) *token.Position { return &token.Position{ Line: e.line, @@ -501,12 +529,12 @@ func (e *Encoder) encodeNil() *ast.NullNode { } func (e *Encoder) encodeInt(v int64) *ast.IntegerNode { - value := fmt.Sprint(v) + value := strconv.FormatInt(v, 10) return ast.Integer(token.New(value, value, e.pos(e.column))) } func (e *Encoder) encodeUint(v uint64) *ast.IntegerNode { - value := fmt.Sprint(v) + value := strconv.FormatUint(v, 10) return ast.Integer(token.New(value, value, e.pos(e.column))) } @@ -523,6 +551,9 @@ func (e *Encoder) encodeFloat(v float64, bitSize int) ast.Node { } value := strconv.FormatFloat(v, 'g', -1, bitSize) if !strings.Contains(value, ".") && !strings.Contains(value, "e") { + if e.autoInt { + return ast.Integer(token.New(value, value, e.pos(e.column))) + } // append x.0 suffix to keep float value context value = fmt.Sprintf("%s.0", value) } @@ -539,6 +570,17 @@ func (e *Encoder) isNeedQuoted(v string) bool { if e.isFlowStyle && strings.ContainsAny(v, `]},'"`) { return true } + if e.isFlowStyle { + for i := 0; i < len(v); i++ { + if v[i] != ':' { + continue + } + if i+1 < len(v) && v[i+1] == '/' { + continue + } + return true + } + } if token.IsNeedQuoted(v) { return true } @@ -557,45 +599,41 @@ func (e *Encoder) encodeString(v string, column int) *ast.StringNode { } func (e *Encoder) encodeBool(v bool) *ast.BoolNode { - value := fmt.Sprint(v) + value := strconv.FormatBool(v) return ast.Bool(token.New(value, value, e.pos(e.column))) } func (e *Encoder) encodeSlice(ctx context.Context, value reflect.Value) (*ast.SequenceNode, error) { if e.indentSequence { - e.column += e.indent + e.column += e.indentNum + defer func() { e.column -= e.indentNum }() } column := e.column sequence := ast.Sequence(token.New("-", "-", e.pos(column)), e.isFlowStyle) for i := 0; i < value.Len(); i++ { node, err := e.encodeValue(ctx, value.Index(i), column) if err != nil { - return nil, errors.Wrapf(err, "failed to encode value for slice") + return nil, err } sequence.Values = append(sequence.Values, node) } - if e.indentSequence { - e.column -= e.indent - } return sequence, nil } func (e *Encoder) encodeArray(ctx context.Context, value reflect.Value) (*ast.SequenceNode, error) { if e.indentSequence { - e.column += e.indent + e.column += e.indentNum + defer func() { e.column -= e.indentNum }() } column := e.column sequence := ast.Sequence(token.New("-", "-", e.pos(column)), e.isFlowStyle) for i := 0; i < value.Len(); i++ { node, err := e.encodeValue(ctx, value.Index(i), column) if err != nil { - return nil, errors.Wrapf(err, "failed to encode value for array") + return nil, err } sequence.Values = append(sequence.Values, node) } - if e.indentSequence { - e.column -= e.indent - } return sequence, nil } @@ -604,10 +642,13 @@ func (e *Encoder) encodeMapItem(ctx context.Context, item MapItem, column int) ( v := reflect.ValueOf(item.Value) value, err := e.encodeValue(ctx, v, column) if err != nil { - return nil, errors.Wrapf(err, "failed to encode MapItem") + return nil, err } if e.isMapNode(value) { - value.AddColumn(e.indent) + value.AddColumn(e.indentNum) + } + if e.isTagAndMapNode(value) { + value.AddColumn(e.indentNum) } return ast.MappingValue( token.New("", "", e.pos(column)), @@ -619,11 +660,11 @@ func (e *Encoder) encodeMapItem(ctx context.Context, item MapItem, column int) ( func (e *Encoder) encodeMapSlice(ctx context.Context, value MapSlice, column int) (*ast.MappingNode, error) { node := ast.Mapping(token.New("", "", e.pos(column)), e.isFlowStyle) for _, item := range value { - value, err := e.encodeMapItem(ctx, item, column) + encoded, err := e.encodeMapItem(ctx, item, column) if err != nil { - return nil, errors.Wrapf(err, "failed to encode MapItem for MapSlice") + return nil, err } - node.Values = append(node.Values, value) + node.Values = append(node.Values, encoded) } return node, nil } @@ -633,7 +674,12 @@ func (e *Encoder) isMapNode(node ast.Node) bool { return ok } -func (e *Encoder) encodeMap(ctx context.Context, value reflect.Value, column int) ast.Node { +func (e *Encoder) isTagAndMapNode(node ast.Node) bool { + tn, ok := node.(*ast.TagNode) + return ok && e.isMapNode(tn.Value) +} + +func (e *Encoder) encodeMap(ctx context.Context, value reflect.Value, column int) (ast.Node, error) { node := ast.Mapping(token.New("", "", e.pos(column)), e.isFlowStyle) keys := make([]interface{}, len(value.MapKeys())) for i, k := range value.MapKeys() { @@ -645,20 +691,35 @@ func (e *Encoder) encodeMap(ctx context.Context, value reflect.Value, column int for _, key := range keys { k := reflect.ValueOf(key) v := value.MapIndex(k) - value, err := e.encodeValue(ctx, v, column) + encoded, err := e.encodeValue(ctx, v, column) if err != nil { - return nil + return nil, err } - if e.isMapNode(value) { - value.AddColumn(e.indent) + if e.isMapNode(encoded) { + encoded.AddColumn(e.indentNum) + } + if e.isTagAndMapNode(encoded) { + encoded.AddColumn(e.indentNum) + } + keyText := fmt.Sprint(key) + vRef := e.toPointer(v) + + // during the second encoding, an anchor is assigned if it is found to be used by an alias. + if aliasName, exists := e.getSmartAlias(vRef); exists { + anchorName := aliasName + anchorNode := ast.Anchor(token.New("&", "&", e.pos(column))) + anchorNode.Name = ast.String(token.New(anchorName, anchorName, e.pos(column))) + anchorNode.Value = encoded + encoded = anchorNode } node.Values = append(node.Values, ast.MappingValue( nil, - e.encodeString(fmt.Sprint(key), column), - value, + e.encodeString(keyText, column), + encoded, )) + e.setSmartAnchor(vRef, keyText) } - return node + return node, nil } // IsZeroer is used to check whether an object is zero to determine @@ -668,7 +729,70 @@ type IsZeroer interface { IsZero() bool } -func (e *Encoder) isZeroValue(v reflect.Value) bool { +func (e *Encoder) isOmittedByOmitZero(v reflect.Value) bool { + kind := v.Kind() + if z, ok := v.Interface().(IsZeroer); ok { + if (kind == reflect.Ptr || kind == reflect.Interface) && v.IsNil() { + return true + } + return z.IsZero() + } + switch kind { + case reflect.String: + return len(v.String()) == 0 + case reflect.Interface, reflect.Ptr, reflect.Slice, reflect.Map: + return v.IsNil() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Struct: + vt := v.Type() + for i := v.NumField() - 1; i >= 0; i-- { + if vt.Field(i).PkgPath != "" { + continue // private field + } + if !e.isOmittedByOmitZero(v.Field(i)) { + return false + } + } + return true + } + return false +} + +func (e *Encoder) isOmittedByOmitEmptyOption(v reflect.Value) bool { + switch v.Kind() { + case reflect.String: + return len(v.String()) == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + case reflect.Slice, reflect.Map: + return v.Len() == 0 + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Bool: + return !v.Bool() + } + return false +} + +// The current implementation of the omitempty tag combines the functionality of encoding/json's omitempty and omitzero tags. +// This stems from a historical decision to respect the implementation of gopkg.in/yaml.v2, but it has caused confusion, +// so we are working to integrate it into the functionality of encoding/json. (However, this will take some time.) +// In the current implementation, in addition to the exclusion conditions of omitempty, +// if a type implements IsZero, that implementation will be used. +// Furthermore, for non-pointer structs, if all fields are eligible for exclusion, +// the struct itself will also be excluded. These behaviors are originally the functionality of omitzero. +func (e *Encoder) isOmittedByOmitEmptyTag(v reflect.Value) bool { kind := v.Kind() if z, ok := v.Interface().(IsZeroer); ok { if (kind == reflect.Ptr || kind == reflect.Interface) && v.IsNil() { @@ -681,9 +805,7 @@ func (e *Encoder) isZeroValue(v reflect.Value) bool { return len(v.String()) == 0 case reflect.Interface, reflect.Ptr: return v.IsNil() - case reflect.Slice: - return v.Len() == 0 - case reflect.Map: + case reflect.Slice, reflect.Map: return v.Len() == 0 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return v.Int() == 0 @@ -699,7 +821,7 @@ func (e *Encoder) isZeroValue(v reflect.Value) bool { if vt.Field(i).PkgPath != "" { continue // private field } - if !e.isZeroValue(v.Field(i)) { + if !e.isOmittedByOmitEmptyTag(v.Field(i)) { return false } } @@ -730,14 +852,14 @@ func (e *Encoder) encodeAnchor(anchorName string, value ast.Node, fieldValue ref anchorNode.Value = value if e.anchorCallback != nil { if err := e.anchorCallback(anchorNode, fieldValue.Interface()); err != nil { - return nil, errors.Wrapf(err, "failed to marshal anchor") + return nil, err } if snode, ok := anchorNode.Name.(*ast.StringNode); ok { anchorName = snode.Value } } if fieldValue.Kind() == reflect.Ptr { - e.anchorPtrToNameMap[fieldValue.Pointer()] = anchorName + e.setAnchor(fieldValue.Pointer(), anchorName) } return anchorNode, nil } @@ -745,9 +867,9 @@ func (e *Encoder) encodeAnchor(anchorName string, value ast.Node, fieldValue ref func (e *Encoder) encodeStruct(ctx context.Context, value reflect.Value, column int) (ast.Node, error) { node := ast.Mapping(token.New("", "", e.pos(column)), e.isFlowStyle) structType := value.Type() - structFieldMap, err := structFieldMap(structType) + fieldMap, err := structFieldMap(structType) if err != nil { - return nil, errors.Wrapf(err, "failed to get struct field map") + return nil, err } hasInlineAnchorField := false var inlineAnchorValue reflect.Value @@ -757,119 +879,190 @@ func (e *Encoder) encodeStruct(ctx context.Context, value reflect.Value, column continue } fieldValue := value.FieldByName(field.Name) - structField := structFieldMap[field.Name] - if structField.IsOmitEmpty && e.isZeroValue(fieldValue) { - // omit encoding + sf := fieldMap[field.Name] + if (e.omitZero || sf.IsOmitZero) && e.isOmittedByOmitZero(fieldValue) { + // omit encoding by omitzero tag or OmitZero option. + continue + } + if e.omitEmpty && e.isOmittedByOmitEmptyOption(fieldValue) { + // omit encoding by OmitEmpty option. + continue + } + if sf.IsOmitEmpty && e.isOmittedByOmitEmptyTag(fieldValue) { + // omit encoding by omitempty tag. continue } ve := e - if !e.isFlowStyle && structField.IsFlow { + if !e.isFlowStyle && sf.IsFlow { ve = &Encoder{} *ve = *e ve.isFlowStyle = true } - value, err := ve.encodeValue(ctx, fieldValue, column) + encoded, err := ve.encodeValue(ctx, fieldValue, column) if err != nil { - return nil, errors.Wrapf(err, "failed to encode value") + return nil, err } - if e.isMapNode(value) { - value.AddColumn(e.indent) + if e.isMapNode(encoded) { + encoded.AddColumn(e.indentNum) } - var key ast.MapKeyNode = e.encodeString(structField.RenderName, column) + var key ast.MapKeyNode = e.encodeString(sf.RenderName, column) switch { - case structField.AnchorName != "": - anchorNode, err := e.encodeAnchor(structField.AnchorName, value, fieldValue, column) + case encoded.Type() == ast.AliasType: + if aliasName := sf.AliasName; aliasName != "" { + alias, ok := encoded.(*ast.AliasNode) + if !ok { + return nil, errors.ErrUnexpectedNodeType(encoded.Type(), ast.AliasType, encoded.GetToken()) + } + got := alias.Value.String() + if aliasName != got { + return nil, fmt.Errorf("expected alias name is %q but got %q", aliasName, got) + } + } + if sf.IsInline { + // if both used alias and inline, output `<<: *alias` + key = ast.MergeKey(token.New("<<", "<<", e.pos(column))) + } + case sf.AnchorName != "": + anchorNode, err := e.encodeAnchor(sf.AnchorName, encoded, fieldValue, column) if err != nil { - return nil, errors.Wrapf(err, "failed to encode anchor") + return nil, err } - value = anchorNode - case structField.IsAutoAlias: - if fieldValue.Kind() != reflect.Ptr { - return nil, xerrors.Errorf( - "%s in struct is not pointer type. but required automatically alias detection", - structField.FieldName, - ) - } - anchorName := e.anchorPtrToNameMap[fieldValue.Pointer()] - if anchorName == "" { - return nil, xerrors.Errorf( - "cannot find anchor name from pointer address for automatically alias detection", - ) - } - aliasName := anchorName - alias := ast.Alias(token.New("*", "*", e.pos(column))) - alias.Value = ast.String(token.New(aliasName, aliasName, e.pos(column))) - value = alias - if structField.IsInline { - // if both used alias and inline, output `<<: *alias` - key = ast.MergeKey(token.New("<<", "<<", e.pos(column))) - } - case structField.AliasName != "": - aliasName := structField.AliasName - alias := ast.Alias(token.New("*", "*", e.pos(column))) - alias.Value = ast.String(token.New(aliasName, aliasName, e.pos(column))) - value = alias - if structField.IsInline { - // if both used alias and inline, output `<<: *alias` - key = ast.MergeKey(token.New("<<", "<<", e.pos(column))) - } - case structField.IsInline: - isAutoAnchor := structField.IsAutoAnchor + encoded = anchorNode + case sf.IsInline: + isAutoAnchor := sf.IsAutoAnchor if !hasInlineAnchorField { hasInlineAnchorField = isAutoAnchor } if isAutoAnchor { inlineAnchorValue = fieldValue } - mapNode, ok := value.(ast.MapNode) + mapNode, ok := encoded.(ast.MapNode) if !ok { // if an inline field is null, skip encoding it - if _, ok := value.(*ast.NullNode); ok { + if _, ok := encoded.(*ast.NullNode); ok { continue } - return nil, xerrors.Errorf("inline value is must be map or struct type") + return nil, errors.New("inline value is must be map or struct type") } mapIter := mapNode.MapRange() for mapIter.Next() { - key := mapIter.Key() - value := mapIter.Value() - keyName := key.GetToken().Value - if structFieldMap.isIncludedRenderName(keyName) { - // if declared same key name, skip encoding this field + mapKey := mapIter.Key() + mapValue := mapIter.Value() + keyName := mapKey.GetToken().Value + if fieldMap.isIncludedRenderName(keyName) { + // if declared the same key name, skip encoding this field continue } - key.AddColumn(-e.indent) - value.AddColumn(-e.indent) - node.Values = append(node.Values, ast.MappingValue(nil, key, value)) + mapKey.AddColumn(-e.indentNum) + mapValue.AddColumn(-e.indentNum) + node.Values = append(node.Values, ast.MappingValue(nil, mapKey, mapValue)) } continue - case structField.IsAutoAnchor: - anchorNode, err := e.encodeAnchor(structField.RenderName, value, fieldValue, column) + case sf.IsAutoAnchor: + anchorNode, err := e.encodeAnchor(sf.RenderName, encoded, fieldValue, column) if err != nil { - return nil, errors.Wrapf(err, "failed to encode anchor") + return nil, err } - value = anchorNode + encoded = anchorNode } - node.Values = append(node.Values, ast.MappingValue(nil, key, value)) + node.Values = append(node.Values, ast.MappingValue(nil, key, encoded)) } if hasInlineAnchorField { - node.AddColumn(e.indent) + node.AddColumn(e.indentNum) anchorName := "anchor" anchorNode := ast.Anchor(token.New("&", "&", e.pos(column))) anchorNode.Name = ast.String(token.New(anchorName, anchorName, e.pos(column))) anchorNode.Value = node if e.anchorCallback != nil { if err := e.anchorCallback(anchorNode, value.Addr().Interface()); err != nil { - return nil, errors.Wrapf(err, "failed to marshal anchor") + return nil, err } if snode, ok := anchorNode.Name.(*ast.StringNode); ok { anchorName = snode.Value } } if inlineAnchorValue.Kind() == reflect.Ptr { - e.anchorPtrToNameMap[inlineAnchorValue.Pointer()] = anchorName + e.setAnchor(inlineAnchorValue.Pointer(), anchorName) } return anchorNode, nil } return node, nil } + +func (e *Encoder) toPointer(v reflect.Value) uintptr { + if e.isInvalidValue(v) { + return 0 + } + + switch v.Type().Kind() { + case reflect.Ptr: + return v.Pointer() + case reflect.Interface: + return e.toPointer(v.Elem()) + case reflect.Slice: + return v.Pointer() + case reflect.Map: + return v.Pointer() + } + return 0 +} + +func (e *Encoder) clearSmartAnchorRef() { + if !e.enableSmartAnchor { + return + } + e.anchorRefToName = make(map[uintptr]string) + e.anchorNameMap = make(map[string]struct{}) +} + +func (e *Encoder) setSmartAnchor(ptr uintptr, name string) { + if !e.enableSmartAnchor { + return + } + e.setAnchor(ptr, e.generateAnchorName(name)) +} + +func (e *Encoder) setAnchor(ptr uintptr, name string) { + if ptr == 0 { + return + } + if name == "" { + return + } + e.anchorRefToName[ptr] = name + e.anchorNameMap[name] = struct{}{} +} + +func (e *Encoder) generateAnchorName(base string) string { + if _, exists := e.anchorNameMap[base]; !exists { + return base + } + for i := 1; i < 100; i++ { + name := base + strconv.Itoa(i) + if _, exists := e.anchorNameMap[name]; exists { + continue + } + return name + } + return "" +} + +func (e *Encoder) getAnchor(ref uintptr) (string, bool) { + anchorName, exists := e.anchorRefToName[ref] + return anchorName, exists +} + +func (e *Encoder) setSmartAlias(name string, ref uintptr) { + if !e.enableSmartAnchor { + return + } + e.aliasRefToName[ref] = name +} + +func (e *Encoder) getSmartAlias(ref uintptr) (string, bool) { + if !e.enableSmartAnchor { + return "", false + } + aliasName, exists := e.aliasRefToName[ref] + return aliasName, exists +} diff --git a/vendor/github.com/goccy/go-yaml/error.go b/vendor/github.com/goccy/go-yaml/error.go index 163dcc558..52d3e7e69 100644 --- a/vendor/github.com/goccy/go-yaml/error.go +++ b/vendor/github.com/goccy/go-yaml/error.go @@ -1,62 +1,77 @@ package yaml import ( + "fmt" + "github.com/goccy/go-yaml/ast" - "golang.org/x/xerrors" + "github.com/goccy/go-yaml/internal/errors" ) var ( - ErrInvalidQuery = xerrors.New("invalid query") - ErrInvalidPath = xerrors.New("invalid path instance") - ErrInvalidPathString = xerrors.New("invalid path string") - ErrNotFoundNode = xerrors.New("node not found") - ErrUnknownCommentPositionType = xerrors.New("unknown comment position type") - ErrInvalidCommentMapValue = xerrors.New("invalid comment map value. it must be not nil value") + ErrInvalidQuery = errors.New("invalid query") + ErrInvalidPath = errors.New("invalid path instance") + ErrInvalidPathString = errors.New("invalid path string") + ErrNotFoundNode = errors.New("node not found") + ErrUnknownCommentPositionType = errors.New("unknown comment position type") + ErrInvalidCommentMapValue = errors.New("invalid comment map value. it must be not nil value") + ErrDecodeRequiredPointerType = errors.New("required pointer type value") + ErrExceededMaxDepth = errors.New("exceeded max depth") + FormatErrorWithToken = errors.FormatError +) + +type ( + SyntaxError = errors.SyntaxError + TypeError = errors.TypeError + OverflowError = errors.OverflowError + DuplicateKeyError = errors.DuplicateKeyError + UnknownFieldError = errors.UnknownFieldError + UnexpectedNodeTypeError = errors.UnexpectedNodeTypeError + Error = errors.Error ) func ErrUnsupportedHeadPositionType(node ast.Node) error { - return xerrors.Errorf("unsupported comment head position for %s", node.Type()) + return fmt.Errorf("unsupported comment head position for %s", node.Type()) } func ErrUnsupportedLinePositionType(node ast.Node) error { - return xerrors.Errorf("unsupported comment line position for %s", node.Type()) + return fmt.Errorf("unsupported comment line position for %s", node.Type()) } func ErrUnsupportedFootPositionType(node ast.Node) error { - return xerrors.Errorf("unsupported comment foot position for %s", node.Type()) + return fmt.Errorf("unsupported comment foot position for %s", node.Type()) } // IsInvalidQueryError whether err is ErrInvalidQuery or not. func IsInvalidQueryError(err error) bool { - return xerrors.Is(err, ErrInvalidQuery) + return errors.Is(err, ErrInvalidQuery) } // IsInvalidPathError whether err is ErrInvalidPath or not. func IsInvalidPathError(err error) bool { - return xerrors.Is(err, ErrInvalidPath) + return errors.Is(err, ErrInvalidPath) } // IsInvalidPathStringError whether err is ErrInvalidPathString or not. func IsInvalidPathStringError(err error) bool { - return xerrors.Is(err, ErrInvalidPathString) + return errors.Is(err, ErrInvalidPathString) } // IsNotFoundNodeError whether err is ErrNotFoundNode or not. func IsNotFoundNodeError(err error) bool { - return xerrors.Is(err, ErrNotFoundNode) + return errors.Is(err, ErrNotFoundNode) } // IsInvalidTokenTypeError whether err is ast.ErrInvalidTokenType or not. func IsInvalidTokenTypeError(err error) bool { - return xerrors.Is(err, ast.ErrInvalidTokenType) + return errors.Is(err, ast.ErrInvalidTokenType) } // IsInvalidAnchorNameError whether err is ast.ErrInvalidAnchorName or not. func IsInvalidAnchorNameError(err error) bool { - return xerrors.Is(err, ast.ErrInvalidAnchorName) + return errors.Is(err, ast.ErrInvalidAnchorName) } // IsInvalidAliasNameError whether err is ast.ErrInvalidAliasName or not. func IsInvalidAliasNameError(err error) bool { - return xerrors.Is(err, ast.ErrInvalidAliasName) + return errors.Is(err, ast.ErrInvalidAliasName) } diff --git a/vendor/github.com/goccy/go-yaml/internal/errors/error.go b/vendor/github.com/goccy/go-yaml/internal/errors/error.go index 7f1ea9af7..b08a3fc60 100644 --- a/vendor/github.com/goccy/go-yaml/internal/errors/error.go +++ b/vendor/github.com/goccy/go-yaml/internal/errors/error.go @@ -1,225 +1,45 @@ package errors import ( - "bytes" + "errors" "fmt" "reflect" + "github.com/goccy/go-yaml/ast" "github.com/goccy/go-yaml/printer" "github.com/goccy/go-yaml/token" - "golang.org/x/xerrors" -) - -const ( - defaultColorize = false - defaultIncludeSource = true ) var ( - ErrDecodeRequiredPointerType = xerrors.New("required pointer type value") + As = errors.As + Is = errors.Is + New = errors.New ) -// Wrapf wrap error for stack trace -func Wrapf(err error, msg string, args ...interface{}) error { - return &wrapError{ - baseError: &baseError{}, - err: xerrors.Errorf(msg, args...), - nextErr: err, - frame: xerrors.Caller(1), - } +const ( + defaultFormatColor = false + defaultIncludeSource = true +) + +type Error interface { + error + GetToken() *token.Token + GetMessage() string + FormatError(bool, bool) string } -// ErrSyntax create syntax error instance with message and token -func ErrSyntax(msg string, tk *token.Token) *syntaxError { - return &syntaxError{ - baseError: &baseError{}, - msg: msg, - token: tk, - frame: xerrors.Caller(1), - } -} +var ( + _ Error = new(SyntaxError) + _ Error = new(TypeError) + _ Error = new(OverflowError) + _ Error = new(DuplicateKeyError) + _ Error = new(UnknownFieldError) + _ Error = new(UnexpectedNodeTypeError) +) -type baseError struct { - state fmt.State - verb rune -} - -func (e *baseError) Error() string { - return "" -} - -func (e *baseError) chainStateAndVerb(err error) { - wrapErr, ok := err.(*wrapError) - if ok { - wrapErr.state = e.state - wrapErr.verb = e.verb - } - syntaxErr, ok := err.(*syntaxError) - if ok { - syntaxErr.state = e.state - syntaxErr.verb = e.verb - } -} - -type wrapError struct { - *baseError - err error - nextErr error - frame xerrors.Frame -} - -type FormatErrorPrinter struct { - xerrors.Printer - Colored bool - InclSource bool -} - -func (e *wrapError) As(target interface{}) bool { - err := e.nextErr - for { - if wrapErr, ok := err.(*wrapError); ok { - err = wrapErr.nextErr - continue - } - break - } - return xerrors.As(err, target) -} - -func (e *wrapError) Unwrap() error { - return e.nextErr -} - -func (e *wrapError) PrettyPrint(p xerrors.Printer, colored, inclSource bool) error { - return e.FormatError(&FormatErrorPrinter{Printer: p, Colored: colored, InclSource: inclSource}) -} - -func (e *wrapError) FormatError(p xerrors.Printer) error { - if _, ok := p.(*FormatErrorPrinter); !ok { - p = &FormatErrorPrinter{ - Printer: p, - Colored: defaultColorize, - InclSource: defaultIncludeSource, - } - } - if e.verb == 'v' && e.state.Flag('+') { - // print stack trace for debugging - p.Print(e.err, "\n") - e.frame.Format(p) - e.chainStateAndVerb(e.nextErr) - return e.nextErr - } - err := e.nextErr - for { - if wrapErr, ok := err.(*wrapError); ok { - err = wrapErr.nextErr - continue - } - break - } - e.chainStateAndVerb(err) - if fmtErr, ok := err.(xerrors.Formatter); ok { - fmtErr.FormatError(p) - } else { - p.Print(err) - } - return nil -} - -type wrapState struct { - org fmt.State -} - -func (s *wrapState) Write(b []byte) (n int, err error) { - return s.org.Write(b) -} - -func (s *wrapState) Width() (wid int, ok bool) { - return s.org.Width() -} - -func (s *wrapState) Precision() (prec int, ok bool) { - return s.org.Precision() -} - -func (s *wrapState) Flag(c int) bool { - // 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. - // ( see https://github.com/golang/xerrors/blob/master/adaptor.go#L39-L43 ) - return false - } - return true -} - -func (e *wrapError) Format(state fmt.State, verb rune) { - e.state = state - e.verb = verb - xerrors.FormatError(e, &wrapState{org: state}, verb) -} - -func (e *wrapError) Error() string { - var buf bytes.Buffer - e.PrettyPrint(&Sink{&buf}, defaultColorize, defaultIncludeSource) - return buf.String() -} - -type syntaxError struct { - *baseError - msg string - token *token.Token - frame xerrors.Frame -} - -func (e *syntaxError) PrettyPrint(p xerrors.Printer, colored, inclSource bool) error { - return e.FormatError(&FormatErrorPrinter{Printer: p, Colored: colored, InclSource: inclSource}) -} - -func (e *syntaxError) 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.msg), colored) - if inclSource { - msg += "\n" + pp.PrintErrorToken(e.token, colored) - } - p.Print(msg) - - if e.verb == 'v' && e.state.Flag('+') { - // %+v - // print stack trace for debugging - e.frame.Format(p) - } - return nil -} - -type PrettyPrinter interface { - PrettyPrint(xerrors.Printer, bool, bool) error -} -type Sink struct{ *bytes.Buffer } - -func (es *Sink) Print(args ...interface{}) { - fmt.Fprint(es.Buffer, args...) -} - -func (es *Sink) Printf(f string, args ...interface{}) { - fmt.Fprintf(es.Buffer, f, args...) -} - -func (es *Sink) Detail() bool { - return false -} - -func (e *syntaxError) Error() string { - var buf bytes.Buffer - e.PrettyPrint(&Sink{&buf}, defaultColorize, defaultIncludeSource) - return buf.String() +type SyntaxError struct { + Message string + Token *token.Token } type TypeError struct { @@ -229,32 +49,198 @@ type TypeError struct { Token *token.Token } -func (e *TypeError) Error() string { +type OverflowError struct { + DstType reflect.Type + SrcNum string + Token *token.Token +} + +type DuplicateKeyError struct { + Message string + Token *token.Token +} + +type UnknownFieldError struct { + Message string + Token *token.Token +} + +type UnexpectedNodeTypeError struct { + Actual ast.NodeType + Expected ast.NodeType + Token *token.Token +} + +// ErrSyntax create syntax error instance with message and token +func ErrSyntax(msg string, tk *token.Token) *SyntaxError { + return &SyntaxError{ + Message: msg, + Token: tk, + } +} + +// 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, + } +} + +// ErrTypeMismatch cerates an type mismatch error instance with token. +func ErrTypeMismatch(dstType, srcType reflect.Type, token *token.Token) *TypeError { + return &TypeError{ + DstType: dstType, + SrcType: srcType, + Token: token, + } +} + +// ErrDuplicateKey creates an duplicate key error instance with token. +func ErrDuplicateKey(msg string, tk *token.Token) *DuplicateKeyError { + return &DuplicateKeyError{ + Message: msg, + Token: tk, + } +} + +// ErrUnknownField creates an unknown field error instance with token. +func ErrUnknownField(msg string, tk *token.Token) *UnknownFieldError { + return &UnknownFieldError{ + Message: msg, + Token: tk, + } +} + +func ErrUnexpectedNodeType(actual, expected ast.NodeType, tk *token.Token) *UnexpectedNodeTypeError { + return &UnexpectedNodeTypeError{ + Actual: actual, + Expected: expected, + Token: tk, + } +} + +func (e *SyntaxError) GetMessage() string { + return e.Message +} + +func (e *SyntaxError) GetToken() *token.Token { + return e.Token +} + +func (e *SyntaxError) Error() string { + return e.FormatError(defaultFormatColor, defaultIncludeSource) +} + +func (e *SyntaxError) FormatError(colored, inclSource bool) string { + return FormatError(e.Message, e.Token, colored, inclSource) +} + +func (e *OverflowError) GetMessage() string { + return e.msg() +} + +func (e *OverflowError) GetToken() *token.Token { + return e.Token +} + +func (e *OverflowError) Error() string { + return e.FormatError(defaultFormatColor, defaultIncludeSource) +} + +func (e *OverflowError) FormatError(colored, inclSource bool) string { + return FormatError(e.msg(), e.Token, colored, inclSource) +} + +func (e *OverflowError) msg() string { + return fmt.Sprintf("cannot unmarshal %s into Go value of type %s ( overflow )", e.SrcNum, e.DstType) +} + +func (e *TypeError) msg() string { if e.StructFieldName != nil { return fmt.Sprintf("cannot unmarshal %s into Go struct field %s of type %s", e.SrcType, *e.StructFieldName, e.DstType) } return fmt.Sprintf("cannot unmarshal %s into Go value of type %s", e.SrcType, e.DstType) } -func (e *TypeError) PrettyPrint(p xerrors.Printer, colored, inclSource bool) error { - return e.FormatError(&FormatErrorPrinter{Printer: p, Colored: colored, InclSource: inclSource}) +func (e *TypeError) GetMessage() string { + return e.msg() } -func (e *TypeError) FormatError(p xerrors.Printer) error { +func (e *TypeError) GetToken() *token.Token { + return e.Token +} + +func (e *TypeError) Error() string { + return e.FormatError(defaultFormatColor, defaultIncludeSource) +} + +func (e *TypeError) FormatError(colored, inclSource bool) string { + return FormatError(e.msg(), e.Token, colored, inclSource) +} + +func (e *DuplicateKeyError) GetMessage() string { + return e.Message +} + +func (e *DuplicateKeyError) GetToken() *token.Token { + return e.Token +} + +func (e *DuplicateKeyError) Error() string { + return e.FormatError(defaultFormatColor, defaultIncludeSource) +} + +func (e *DuplicateKeyError) FormatError(colored, inclSource bool) string { + return FormatError(e.Message, e.Token, colored, inclSource) +} + +func (e *UnknownFieldError) GetMessage() string { + return e.Message +} + +func (e *UnknownFieldError) GetToken() *token.Token { + return e.Token +} + +func (e *UnknownFieldError) Error() string { + return e.FormatError(defaultFormatColor, defaultIncludeSource) +} + +func (e *UnknownFieldError) FormatError(colored, inclSource bool) string { + return FormatError(e.Message, e.Token, colored, inclSource) +} + +func (e *UnexpectedNodeTypeError) GetMessage() string { + return e.msg() +} + +func (e *UnexpectedNodeTypeError) GetToken() *token.Token { + return e.Token +} + +func (e *UnexpectedNodeTypeError) Error() string { + return e.FormatError(defaultFormatColor, defaultIncludeSource) +} + +func (e *UnexpectedNodeTypeError) FormatError(colored, inclSource bool) string { + return FormatError(e.msg(), e.Token, colored, inclSource) +} + +func (e *UnexpectedNodeTypeError) msg() string { + return fmt.Sprintf("%s was used where %s is expected", e.Actual.YAMLName(), e.Expected.YAMLName()) +} + +func FormatError(errMsg string, token *token.Token, colored, inclSource bool) string { var pp printer.Printer - - var colored, inclSource bool - if fep, ok := p.(*FormatErrorPrinter); ok { - colored = fep.Colored - inclSource = fep.InclSource + if token == nil { + return pp.PrintErrorMessage(errMsg, colored) } - - pos := fmt.Sprintf("[%d:%d] ", e.Token.Position.Line, e.Token.Position.Column) - msg := pp.PrintErrorMessage(fmt.Sprintf("%s%s", pos, e.Error()), colored) + pos := fmt.Sprintf("[%d:%d] ", token.Position.Line, token.Position.Column) + msg := pp.PrintErrorMessage(fmt.Sprintf("%s%s", pos, errMsg), colored) if inclSource { - msg += "\n" + pp.PrintErrorToken(e.Token, colored) + msg += "\n" + pp.PrintErrorToken(token, colored) } - p.Print(msg) - - return nil + return msg } diff --git a/vendor/github.com/goccy/go-yaml/internal/format/format.go b/vendor/github.com/goccy/go-yaml/internal/format/format.go new file mode 100644 index 000000000..2d55652ff --- /dev/null +++ b/vendor/github.com/goccy/go-yaml/internal/format/format.go @@ -0,0 +1,539 @@ +package format + +import ( + "strings" + + "github.com/goccy/go-yaml/ast" + "github.com/goccy/go-yaml/token" +) + +func FormatNodeWithResolvedAlias(n ast.Node, anchorNodeMap map[string]ast.Node) string { + tk := getFirstToken(n) + if tk == nil { + return "" + } + formatter := newFormatter(tk, hasComment(n)) + formatter.anchorNodeMap = anchorNodeMap + return formatter.format(n) +} + +func FormatNode(n ast.Node) string { + tk := getFirstToken(n) + if tk == nil { + return "" + } + return newFormatter(tk, hasComment(n)).format(n) +} + +func FormatFile(file *ast.File) string { + if len(file.Docs) == 0 { + return "" + } + tk := getFirstToken(file.Docs[0]) + if tk == nil { + return "" + } + return newFormatter(tk, hasCommentFile(file)).formatFile(file) +} + +func hasCommentFile(f *ast.File) bool { + for _, doc := range f.Docs { + if hasComment(doc.Body) { + return true + } + } + return false +} + +func hasComment(n ast.Node) bool { + if n == nil { + return false + } + switch nn := n.(type) { + case *ast.DocumentNode: + return hasComment(nn.Body) + case *ast.NullNode: + return nn.Comment != nil + case *ast.BoolNode: + return nn.Comment != nil + case *ast.IntegerNode: + return nn.Comment != nil + case *ast.FloatNode: + return nn.Comment != nil + case *ast.StringNode: + return nn.Comment != nil + case *ast.InfinityNode: + return nn.Comment != nil + case *ast.NanNode: + return nn.Comment != nil + case *ast.LiteralNode: + return nn.Comment != nil + case *ast.DirectiveNode: + if nn.Comment != nil { + return true + } + for _, value := range nn.Values { + if hasComment(value) { + return true + } + } + case *ast.TagNode: + if nn.Comment != nil { + return true + } + return hasComment(nn.Value) + case *ast.MappingNode: + if nn.Comment != nil || nn.FootComment != nil { + return true + } + for _, value := range nn.Values { + if value.Comment != nil || value.FootComment != nil { + return true + } + if hasComment(value.Key) { + return true + } + if hasComment(value.Value) { + return true + } + } + case *ast.MappingKeyNode: + return nn.Comment != nil + case *ast.MergeKeyNode: + return nn.Comment != nil + case *ast.SequenceNode: + if nn.Comment != nil || nn.FootComment != nil { + return true + } + for _, entry := range nn.Entries { + if entry.Comment != nil || entry.HeadComment != nil || entry.LineComment != nil { + return true + } + if hasComment(entry.Value) { + return true + } + } + case *ast.AnchorNode: + if nn.Comment != nil { + return true + } + if hasComment(nn.Name) || hasComment(nn.Value) { + return true + } + case *ast.AliasNode: + if nn.Comment != nil { + return true + } + if hasComment(nn.Value) { + return true + } + } + return false +} + +func getFirstToken(n ast.Node) *token.Token { + if n == nil { + return nil + } + switch nn := n.(type) { + case *ast.DocumentNode: + if nn.Start != nil { + return nn.Start + } + return getFirstToken(nn.Body) + case *ast.NullNode: + return nn.Token + case *ast.BoolNode: + return nn.Token + case *ast.IntegerNode: + return nn.Token + case *ast.FloatNode: + return nn.Token + case *ast.StringNode: + return nn.Token + case *ast.InfinityNode: + return nn.Token + case *ast.NanNode: + return nn.Token + case *ast.LiteralNode: + return nn.Start + case *ast.DirectiveNode: + return nn.Start + case *ast.TagNode: + return nn.Start + case *ast.MappingNode: + if nn.IsFlowStyle { + return nn.Start + } + if len(nn.Values) == 0 { + return nn.Start + } + return getFirstToken(nn.Values[0].Key) + case *ast.MappingKeyNode: + return nn.Start + case *ast.MergeKeyNode: + return nn.Token + case *ast.SequenceNode: + return nn.Start + case *ast.AnchorNode: + return nn.Start + case *ast.AliasNode: + return nn.Start + } + return nil +} + +type Formatter struct { + existsComment bool + tokenToOriginMap map[*token.Token]string + anchorNodeMap map[string]ast.Node +} + +func newFormatter(tk *token.Token, existsComment bool) *Formatter { + tokenToOriginMap := make(map[*token.Token]string) + for tk.Prev != nil { + tk = tk.Prev + } + tokenToOriginMap[tk] = tk.Origin + + var origin string + for tk.Next != nil { + tk = tk.Next + if tk.Type == token.CommentType { + origin += strings.Repeat("\n", strings.Count(normalizeNewLineChars(tk.Origin), "\n")) + continue + } + origin += tk.Origin + tokenToOriginMap[tk] = origin + origin = "" + } + return &Formatter{ + existsComment: existsComment, + tokenToOriginMap: tokenToOriginMap, + } +} + +func getIndentNumByFirstLineToken(tk *token.Token) int { + defaultIndent := tk.Position.Column - 1 + + // key: value + // ^ + // next + if tk.Type == token.SequenceEntryType { + // If the current token is the sequence entry. + // the indent is calculated from the column value of the current token. + return defaultIndent + } + + // key: value + // ^ + // next + if tk.Next != nil && tk.Next.Type == token.MappingValueType { + // If the current token is the key in the mapping-value, + // the indent is calculated from the column value of the current token. + return defaultIndent + } + + if tk.Prev == nil { + return defaultIndent + } + prev := tk.Prev + + // key: value + // ^ + // prev + if prev.Type == token.MappingValueType { + // If the current token is the value in the mapping-value, + // the indent is calculated from the column value of the key two steps back. + if prev.Prev == nil { + return defaultIndent + } + return prev.Prev.Position.Column - 1 + } + + // - value + // ^ + // prev + if prev.Type == token.SequenceEntryType { + // If the value is not a mapping-value and the previous token was a sequence entry, + // the indent is calculated using the column value of the sequence entry token. + return prev.Position.Column - 1 + } + + return defaultIndent +} + +func (f *Formatter) format(n ast.Node) string { + return f.trimSpacePrefix( + f.trimIndentSpace( + getIndentNumByFirstLineToken(getFirstToken(n)), + f.trimNewLineCharPrefix(f.formatNode(n)), + ), + ) +} + +func (f *Formatter) formatFile(file *ast.File) string { + if len(file.Docs) == 0 { + return "" + } + var ret string + for _, doc := range file.Docs { + ret += f.formatDocument(doc) + } + return ret +} + +func (f *Formatter) origin(tk *token.Token) string { + if tk == nil { + return "" + } + if f.existsComment { + return tk.Origin + } + return f.tokenToOriginMap[tk] +} + +func (f *Formatter) formatDocument(n *ast.DocumentNode) string { + return f.origin(n.Start) + f.formatNode(n.Body) + f.origin(n.End) +} + +func (f *Formatter) formatNull(n *ast.NullNode) string { + return f.origin(n.Token) + f.formatCommentGroup(n.Comment) +} + +func (f *Formatter) formatString(n *ast.StringNode) string { + return f.origin(n.Token) + f.formatCommentGroup(n.Comment) +} + +func (f *Formatter) formatInteger(n *ast.IntegerNode) string { + return f.origin(n.Token) + f.formatCommentGroup(n.Comment) +} + +func (f *Formatter) formatFloat(n *ast.FloatNode) string { + return f.origin(n.Token) + f.formatCommentGroup(n.Comment) +} + +func (f *Formatter) formatBool(n *ast.BoolNode) string { + return f.origin(n.Token) + f.formatCommentGroup(n.Comment) +} + +func (f *Formatter) formatInfinity(n *ast.InfinityNode) string { + return f.origin(n.Token) + f.formatCommentGroup(n.Comment) +} + +func (f *Formatter) formatNan(n *ast.NanNode) string { + return f.origin(n.Token) + f.formatCommentGroup(n.Comment) +} + +func (f *Formatter) formatLiteral(n *ast.LiteralNode) string { + return f.origin(n.Start) + f.formatCommentGroup(n.Comment) + f.origin(n.Value.Token) +} + +func (f *Formatter) formatMergeKey(n *ast.MergeKeyNode) string { + return f.origin(n.Token) +} + +func (f *Formatter) formatMappingValue(n *ast.MappingValueNode) string { + return f.formatCommentGroup(n.Comment) + + f.origin(n.Key.GetToken()) + ":" + f.formatCommentGroup(n.Key.GetComment()) + f.formatNode(n.Value) + + f.formatCommentGroup(n.FootComment) +} + +func (f *Formatter) formatDirective(n *ast.DirectiveNode) string { + ret := f.origin(n.Start) + f.formatNode(n.Name) + for _, val := range n.Values { + ret += f.formatNode(val) + } + return ret +} + +func (f *Formatter) formatMapping(n *ast.MappingNode) string { + var ret string + if n.IsFlowStyle { + ret = f.origin(n.Start) + } + ret += f.formatCommentGroup(n.Comment) + for _, value := range n.Values { + if value.CollectEntry != nil { + ret += f.origin(value.CollectEntry) + } + ret += f.formatMappingValue(value) + } + if n.IsFlowStyle { + ret += f.origin(n.End) + } + return ret +} + +func (f *Formatter) formatTag(n *ast.TagNode) string { + return f.origin(n.Start) + f.formatNode(n.Value) +} + +func (f *Formatter) formatMappingKey(n *ast.MappingKeyNode) string { + return f.origin(n.Start) + f.formatNode(n.Value) +} + +func (f *Formatter) formatSequence(n *ast.SequenceNode) string { + var ret string + if n.IsFlowStyle { + ret = f.origin(n.Start) + } + if n.Comment != nil { + // add head comment. + ret += f.formatCommentGroup(n.Comment) + } + for _, entry := range n.Entries { + ret += f.formatNode(entry) + } + if n.IsFlowStyle { + ret += f.origin(n.End) + } + ret += f.formatCommentGroup(n.FootComment) + return ret +} + +func (f *Formatter) formatSequenceEntry(n *ast.SequenceEntryNode) string { + return f.formatCommentGroup(n.HeadComment) + f.origin(n.Start) + f.formatCommentGroup(n.LineComment) + f.formatNode(n.Value) +} + +func (f *Formatter) formatAnchor(n *ast.AnchorNode) string { + return f.origin(n.Start) + f.formatNode(n.Name) + f.formatNode(n.Value) +} + +func (f *Formatter) formatAlias(n *ast.AliasNode) string { + if f.anchorNodeMap != nil { + anchorName := n.Value.GetToken().Value + node := f.anchorNodeMap[anchorName] + if node != nil { + formatted := f.formatNode(node) + // If formatted text contains newline characters, indentation needs to be considered. + if strings.Contains(formatted, "\n") { + // If the first character is not a newline, the first line should be output without indentation. + isIgnoredFirstLine := !strings.HasPrefix(formatted, "\n") + formatted = f.addIndentSpace(n.GetToken().Position.IndentNum, formatted, isIgnoredFirstLine) + } + return formatted + } + } + return f.origin(n.Start) + f.formatNode(n.Value) +} + +func (f *Formatter) formatNode(n ast.Node) string { + switch nn := n.(type) { + case *ast.DocumentNode: + return f.formatDocument(nn) + case *ast.NullNode: + return f.formatNull(nn) + case *ast.BoolNode: + return f.formatBool(nn) + case *ast.IntegerNode: + return f.formatInteger(nn) + case *ast.FloatNode: + return f.formatFloat(nn) + case *ast.StringNode: + return f.formatString(nn) + case *ast.InfinityNode: + return f.formatInfinity(nn) + case *ast.NanNode: + return f.formatNan(nn) + case *ast.LiteralNode: + return f.formatLiteral(nn) + case *ast.DirectiveNode: + return f.formatDirective(nn) + case *ast.TagNode: + return f.formatTag(nn) + case *ast.MappingNode: + return f.formatMapping(nn) + case *ast.MappingKeyNode: + return f.formatMappingKey(nn) + case *ast.MappingValueNode: + return f.formatMappingValue(nn) + case *ast.MergeKeyNode: + return f.formatMergeKey(nn) + case *ast.SequenceNode: + return f.formatSequence(nn) + case *ast.SequenceEntryNode: + return f.formatSequenceEntry(nn) + case *ast.AnchorNode: + return f.formatAnchor(nn) + case *ast.AliasNode: + return f.formatAlias(nn) + } + return "" +} + +func (f *Formatter) formatCommentGroup(g *ast.CommentGroupNode) string { + if g == nil { + return "" + } + var ret string + for _, cm := range g.Comments { + ret += f.formatComment(cm) + } + return ret +} + +func (f *Formatter) formatComment(n *ast.CommentNode) string { + if n == nil { + return "" + } + return n.Token.Origin +} + +// nolint: unused +func (f *Formatter) formatIndent(col int) string { + if col <= 1 { + return "" + } + return strings.Repeat(" ", col-1) +} + +func (f *Formatter) trimNewLineCharPrefix(v string) string { + return strings.TrimLeftFunc(v, func(r rune) bool { + return r == '\n' || r == '\r' + }) +} + +func (f *Formatter) trimSpacePrefix(v string) string { + return strings.TrimLeftFunc(v, func(r rune) bool { + return r == ' ' + }) +} + +func (f *Formatter) trimIndentSpace(trimIndentNum int, v string) string { + if trimIndentNum == 0 { + return v + } + lines := strings.Split(normalizeNewLineChars(v), "\n") + out := make([]string, 0, len(lines)) + for _, line := range lines { + var cnt int + out = append(out, strings.TrimLeftFunc(line, func(r rune) bool { + cnt++ + return r == ' ' && cnt <= trimIndentNum + })) + } + return strings.Join(out, "\n") +} + +func (f *Formatter) addIndentSpace(indentNum int, v string, isIgnoredFirstLine bool) string { + if indentNum == 0 { + return v + } + indent := strings.Repeat(" ", indentNum) + lines := strings.Split(normalizeNewLineChars(v), "\n") + out := make([]string, 0, len(lines)) + for idx, line := range lines { + if line == "" || (isIgnoredFirstLine && idx == 0) { + out = append(out, line) + continue + } + out = append(out, indent+line) + } + return strings.Join(out, "\n") +} + +// normalizeNewLineChars normalize CRLF and CR to LF. +func normalizeNewLineChars(v string) string { + return strings.ReplaceAll(strings.ReplaceAll(v, "\r\n", "\n"), "\r", "\n") +} diff --git a/vendor/github.com/goccy/go-yaml/option.go b/vendor/github.com/goccy/go-yaml/option.go index eab5d43a8..32dbe2313 100644 --- a/vendor/github.com/goccy/go-yaml/option.go +++ b/vendor/github.com/goccy/go-yaml/option.go @@ -1,6 +1,7 @@ package yaml import ( + "context" "io" "reflect" @@ -50,11 +51,10 @@ func Validator(v StructValidator) DecodeOption { } } -// Strict enable DisallowUnknownField and DisallowDuplicateKey +// Strict enable DisallowUnknownField func Strict() DecodeOption { return func(d *Decoder) error { d.disallowUnknownField = true - d.disallowDuplicateKey = true return nil } } @@ -69,10 +69,10 @@ func DisallowUnknownField() DecodeOption { } } -// DisallowDuplicateKey causes an error when mapping keys that are duplicates -func DisallowDuplicateKey() DecodeOption { +// AllowDuplicateMapKey ignore syntax error when mapping keys that are duplicates. +func AllowDuplicateMapKey() DecodeOption { return func(d *Decoder) error { - d.disallowDuplicateKey = true + d.allowDuplicateMapKey = true return nil } } @@ -102,20 +102,32 @@ func UseJSONUnmarshaler() DecodeOption { func CustomUnmarshaler[T any](unmarshaler func(*T, []byte) error) DecodeOption { return func(d *Decoder) error { var typ *T - d.customUnmarshalerMap[reflect.TypeOf(typ)] = func(v interface{}, b []byte) error { + d.customUnmarshalerMap[reflect.TypeOf(typ)] = func(ctx context.Context, v interface{}, b []byte) error { return unmarshaler(v.(*T), b) } return nil } } +// CustomUnmarshalerContext overrides any decoding process for the type specified in generics. +// Similar to CustomUnmarshaler, but allows passing a context to the unmarshaler function. +func CustomUnmarshalerContext[T any](unmarshaler func(context.Context, *T, []byte) error) DecodeOption { + return func(d *Decoder) error { + var typ *T + d.customUnmarshalerMap[reflect.TypeOf(typ)] = func(ctx context.Context, v interface{}, b []byte) error { + return unmarshaler(ctx, v.(*T), b) + } + return nil + } +} + // EncodeOption functional option type for Encoder type EncodeOption func(e *Encoder) error // Indent change indent number func Indent(spaces int) EncodeOption { return func(e *Encoder) error { - e.indent = spaces + e.indentNum = spaces return nil } } @@ -144,6 +156,18 @@ func Flow(isFlowStyle bool) EncodeOption { } } +// WithSmartAnchor when multiple map values share the same pointer, +// an anchor is automatically assigned to the first occurrence, and aliases are used for subsequent elements. +// The map key name is used as the anchor name by default. +// If key names conflict, a suffix is automatically added to avoid collisions. +// This is an experimental feature and cannot be used simultaneously with anchor tags. +func WithSmartAnchor() EncodeOption { + return func(e *Encoder) error { + e.enableSmartAnchor = true + return nil + } +} + // UseLiteralStyleIfMultiline causes encoding multiline strings with a literal syntax, // no matter what characters they include func UseLiteralStyleIfMultiline(useLiteralStyleIfMultiline bool) EncodeOption { @@ -188,13 +212,54 @@ func UseJSONMarshaler() EncodeOption { func CustomMarshaler[T any](marshaler func(T) ([]byte, error)) EncodeOption { return func(e *Encoder) error { var typ T - e.customMarshalerMap[reflect.TypeOf(typ)] = func(v interface{}) ([]byte, error) { + e.customMarshalerMap[reflect.TypeOf(typ)] = func(ctx context.Context, v interface{}) ([]byte, error) { return marshaler(v.(T)) } return nil } } +// CustomMarshalerContext overrides any encoding process for the type specified in generics. +// Similar to CustomMarshaler, but allows passing a context to the marshaler function. +func CustomMarshalerContext[T any](marshaler func(context.Context, T) ([]byte, error)) EncodeOption { + return func(e *Encoder) error { + var typ T + e.customMarshalerMap[reflect.TypeOf(typ)] = func(ctx context.Context, v interface{}) ([]byte, error) { + return marshaler(ctx, v.(T)) + } + return nil + } +} + +// AutoInt automatically converts floating-point numbers to integers when the fractional part is zero. +// For example, a value of 1.0 will be encoded as 1. +func AutoInt() EncodeOption { + return func(e *Encoder) error { + e.autoInt = true + return nil + } +} + +// OmitEmpty behaves in the same way as the interpretation of the omitempty tag in the encoding/json library. +// set on all the fields. +// In the current implementation, the omitempty tag is not implemented in the same way as encoding/json, +// so please specify this option if you expect the same behavior. +func OmitEmpty() EncodeOption { + return func(e *Encoder) error { + e.omitEmpty = true + return nil + } +} + +// OmitZero forces the encoder to assume an `omitzero` struct tag is +// set on all the fields. See `Marshal` commentary for the `omitzero` tag logic. +func OmitZero() EncodeOption { + return func(e *Encoder) error { + e.omitZero = true + return nil + } +} + // CommentPosition type of the position for comment. type CommentPosition int diff --git a/vendor/github.com/goccy/go-yaml/parser/color.go b/vendor/github.com/goccy/go-yaml/parser/color.go new file mode 100644 index 000000000..aeee0dce1 --- /dev/null +++ b/vendor/github.com/goccy/go-yaml/parser/color.go @@ -0,0 +1,28 @@ +package parser + +import "fmt" + +const ( + colorFgHiBlack int = iota + 90 + colorFgHiRed + colorFgHiGreen + colorFgHiYellow + colorFgHiBlue + colorFgHiMagenta + colorFgHiCyan +) + +var colorTable = []int{ + colorFgHiRed, + colorFgHiGreen, + colorFgHiYellow, + colorFgHiBlue, + colorFgHiMagenta, + colorFgHiCyan, +} + +func colorize(idx int, content string) string { + colorIdx := idx % len(colorTable) + color := colorTable[colorIdx] + return fmt.Sprintf("\x1b[1;%dm", color) + content + "\x1b[22;0m" +} diff --git a/vendor/github.com/goccy/go-yaml/parser/context.go b/vendor/github.com/goccy/go-yaml/parser/context.go index 42cc4f8fa..1584b3ecd 100644 --- a/vendor/github.com/goccy/go-yaml/parser/context.go +++ b/vendor/github.com/goccy/go-yaml/parser/context.go @@ -9,11 +9,15 @@ import ( // context context at parsing type context struct { - parent *context - idx int + tokenRef *tokenRef + path string + isFlow bool +} + +type tokenRef struct { + tokens []*Token size int - tokens token.Tokens - path string + idx int } var pathSpecialChars = []string{ @@ -36,91 +40,28 @@ func normalizePath(path string) string { return path } -func (c *context) withChild(path string) *context { - ctx := c.copy() - path = normalizePath(path) - ctx.path += fmt.Sprintf(".%s", path) - return ctx -} - -func (c *context) withIndex(idx uint) *context { - ctx := c.copy() - ctx.path += fmt.Sprintf("[%d]", idx) - return ctx -} - -func (c *context) copy() *context { - return &context{ - parent: c, - idx: c.idx, - size: c.size, - tokens: append(token.Tokens{}, c.tokens...), - path: c.path, - } -} - -func (c *context) next() bool { - return c.idx < c.size -} - -func (c *context) previousToken() *token.Token { - if c.idx > 0 { - return c.tokens[c.idx-1] - } - return nil -} - -func (c *context) insertToken(idx int, tk *token.Token) { - if c.parent != nil { - c.parent.insertToken(idx, tk) - } - if c.size < idx { - return - } - if c.size == idx { - curToken := c.tokens[c.size-1] - tk.Next = curToken - curToken.Prev = tk - - c.tokens = append(c.tokens, tk) - c.size = len(c.tokens) - return - } - - curToken := c.tokens[idx] - tk.Next = curToken - curToken.Prev = tk - - c.tokens = append(c.tokens[:idx+1], c.tokens[idx:]...) - c.tokens[idx] = tk - c.size = len(c.tokens) -} - -func (c *context) currentToken() *token.Token { - if c.idx >= c.size { +func (c *context) currentToken() *Token { + if c.tokenRef.idx >= c.tokenRef.size { return nil } - return c.tokens[c.idx] + return c.tokenRef.tokens[c.tokenRef.idx] } -func (c *context) nextToken() *token.Token { - if c.idx+1 >= c.size { +func (c *context) isComment() bool { + return c.currentToken().Type() == token.CommentType +} + +func (c *context) nextToken() *Token { + if c.tokenRef.idx+1 >= c.tokenRef.size { return nil } - return c.tokens[c.idx+1] + return c.tokenRef.tokens[c.tokenRef.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] - if tk.Type == token.CommentType { +func (c *context) nextNotCommentToken() *Token { + for i := c.tokenRef.idx + 1; i < c.tokenRef.size; i++ { + tk := c.tokenRef.tokens[i] + if tk.Type() == token.CommentType { continue } return tk @@ -128,65 +69,119 @@ func (c *context) nextNotCommentToken() *token.Token { return nil } -func (c *context) afterNextNotCommentToken() *token.Token { - notCommentTokenCount := 0 - for i := c.idx + 1; i < c.size; i++ { - tk := c.tokens[i] - if tk.Type == token.CommentType { - continue - } - notCommentTokenCount++ - if notCommentTokenCount == 2 { - return tk - } - } - return nil +func (c *context) isTokenNotFound() bool { + return c.currentToken() == nil } -func (c *context) isCurrentCommentToken() bool { - tk := c.currentToken() - if tk == nil { - return false +func (c *context) withGroup(g *TokenGroup) *context { + ctx := *c + ctx.tokenRef = &tokenRef{ + tokens: g.Tokens, + size: len(g.Tokens), } - return tk.Type == token.CommentType + return &ctx } -func (c *context) progressIgnoreComment(num int) { - if c.parent != nil { - c.parent.progressIgnoreComment(num) +func (c *context) withChild(path string) *context { + ctx := *c + ctx.path = c.path + "." + normalizePath(path) + return &ctx +} + +func (c *context) withIndex(idx uint) *context { + ctx := *c + ctx.path = c.path + "[" + fmt.Sprint(idx) + "]" + return &ctx +} + +func (c *context) withFlow(isFlow bool) *context { + ctx := *c + ctx.isFlow = isFlow + return &ctx +} + +func newContext() *context { + return &context{ + path: "$", } - if c.size <= c.idx+num { - c.idx = c.size +} + +func (c *context) goNext() { + ref := c.tokenRef + if ref.size <= ref.idx+1 { + ref.idx = ref.size } else { - c.idx += num + ref.idx++ } } -func (c *context) progress(num int) { - if c.isCurrentCommentToken() { +func (c *context) next() bool { + return c.tokenRef.idx < c.tokenRef.size +} + +func (c *context) insertNullToken(tk *Token) *Token { + nullToken := c.createImplicitNullToken(tk) + c.insertToken(nullToken) + c.goNext() + + return nullToken +} + +func (c *context) addNullValueToken(tk *Token) *Token { + nullToken := c.createImplicitNullToken(tk) + rawTk := nullToken.RawToken() + + // add space for map or sequence value. + rawTk.Position.Column++ + + c.addToken(nullToken) + c.goNext() + + return nullToken +} + +func (c *context) createImplicitNullToken(base *Token) *Token { + pos := *(base.RawToken().Position) + pos.Column++ + tk := token.New("null", " null", &pos) + tk.Type = token.ImplicitNullType + return &Token{Token: tk} +} + +func (c *context) insertToken(tk *Token) { + ref := c.tokenRef + idx := ref.idx + if ref.size < idx { return } - c.progressIgnoreComment(num) + if ref.size == idx { + curToken := ref.tokens[ref.size-1] + tk.RawToken().Next = curToken.RawToken() + curToken.RawToken().Prev = tk.RawToken() + + ref.tokens = append(ref.tokens, tk) + ref.size = len(ref.tokens) + return + } + + curToken := ref.tokens[idx] + tk.RawToken().Next = curToken.RawToken() + curToken.RawToken().Prev = tk.RawToken() + + ref.tokens = append(ref.tokens[:idx+1], ref.tokens[idx:]...) + ref.tokens[idx] = tk + ref.size = len(ref.tokens) } -func newContext(tokens token.Tokens, mode Mode) *context { - filteredTokens := []*token.Token{} - if mode&ParseComments != 0 { - filteredTokens = tokens - } else { - for _, tk := range tokens { - if tk.Type == token.CommentType { - continue - } - // keep prev/next reference between tokens containing comments - // https://github.com/goccy/go-yaml/issues/254 - filteredTokens = append(filteredTokens, tk) - } - } - return &context{ - idx: 0, - size: len(filteredTokens), - tokens: token.Tokens(filteredTokens), - path: "$", +func (c *context) addToken(tk *Token) { + ref := c.tokenRef + lastTk := ref.tokens[ref.size-1] + if lastTk.Group != nil { + lastTk = lastTk.Group.Last() } + lastTk.RawToken().Next = tk.RawToken() + tk.RawToken().Prev = lastTk.RawToken() + + ref.tokens = append(ref.tokens, tk) + ref.size = len(ref.tokens) } diff --git a/vendor/github.com/goccy/go-yaml/parser/node.go b/vendor/github.com/goccy/go-yaml/parser/node.go new file mode 100644 index 000000000..8d35554ee --- /dev/null +++ b/vendor/github.com/goccy/go-yaml/parser/node.go @@ -0,0 +1,257 @@ +package parser + +import ( + "fmt" + + "github.com/goccy/go-yaml/ast" + "github.com/goccy/go-yaml/internal/errors" + "github.com/goccy/go-yaml/token" +) + +func newMappingNode(ctx *context, tk *Token, isFlow bool, values ...*ast.MappingValueNode) (*ast.MappingNode, error) { + node := ast.Mapping(tk.RawToken(), isFlow, values...) + node.SetPath(ctx.path) + return node, nil +} + +func newMappingValueNode(ctx *context, colonTk, entryTk *Token, key ast.MapKeyNode, value ast.Node) (*ast.MappingValueNode, error) { + node := ast.MappingValue(colonTk.RawToken(), key, value) + node.SetPath(ctx.path) + node.CollectEntry = entryTk.RawToken() + if key.GetToken().Position.Line == value.GetToken().Position.Line { + // originally key was commented, but now that null value has been added, value must be commented. + if err := setLineComment(ctx, value, colonTk); err != nil { + return nil, err + } + // set line comment by colonTk or entryTk. + if err := setLineComment(ctx, value, entryTk); err != nil { + return nil, err + } + } else { + if err := setLineComment(ctx, key, colonTk); err != nil { + return nil, err + } + // set line comment by colonTk or entryTk. + if err := setLineComment(ctx, key, entryTk); err != nil { + return nil, err + } + } + return node, nil +} + +func newMappingKeyNode(ctx *context, tk *Token) (*ast.MappingKeyNode, error) { + node := ast.MappingKey(tk.RawToken()) + node.SetPath(ctx.path) + if err := setLineComment(ctx, node, tk); err != nil { + return nil, err + } + return node, nil +} + +func newAnchorNode(ctx *context, tk *Token) (*ast.AnchorNode, error) { + node := ast.Anchor(tk.RawToken()) + node.SetPath(ctx.path) + if err := setLineComment(ctx, node, tk); err != nil { + return nil, err + } + return node, nil +} + +func newAliasNode(ctx *context, tk *Token) (*ast.AliasNode, error) { + node := ast.Alias(tk.RawToken()) + node.SetPath(ctx.path) + if err := setLineComment(ctx, node, tk); err != nil { + return nil, err + } + return node, nil +} + +func newDirectiveNode(ctx *context, tk *Token) (*ast.DirectiveNode, error) { + node := ast.Directive(tk.RawToken()) + node.SetPath(ctx.path) + if err := setLineComment(ctx, node, tk); err != nil { + return nil, err + } + return node, nil +} + +func newMergeKeyNode(ctx *context, tk *Token) (*ast.MergeKeyNode, error) { + node := ast.MergeKey(tk.RawToken()) + node.SetPath(ctx.path) + if err := setLineComment(ctx, node, tk); err != nil { + return nil, err + } + return node, nil +} + +func newNullNode(ctx *context, tk *Token) (*ast.NullNode, error) { + node := ast.Null(tk.RawToken()) + node.SetPath(ctx.path) + if err := setLineComment(ctx, node, tk); err != nil { + return nil, err + } + return node, nil +} + +func newBoolNode(ctx *context, tk *Token) (*ast.BoolNode, error) { + node := ast.Bool(tk.RawToken()) + node.SetPath(ctx.path) + if err := setLineComment(ctx, node, tk); err != nil { + return nil, err + } + return node, nil +} + +func newIntegerNode(ctx *context, tk *Token) (*ast.IntegerNode, error) { + node := ast.Integer(tk.RawToken()) + node.SetPath(ctx.path) + if err := setLineComment(ctx, node, tk); err != nil { + return nil, err + } + return node, nil +} + +func newFloatNode(ctx *context, tk *Token) (*ast.FloatNode, error) { + node := ast.Float(tk.RawToken()) + node.SetPath(ctx.path) + if err := setLineComment(ctx, node, tk); err != nil { + return nil, err + } + return node, nil +} + +func newInfinityNode(ctx *context, tk *Token) (*ast.InfinityNode, error) { + node := ast.Infinity(tk.RawToken()) + node.SetPath(ctx.path) + if err := setLineComment(ctx, node, tk); err != nil { + return nil, err + } + return node, nil +} + +func newNanNode(ctx *context, tk *Token) (*ast.NanNode, error) { + node := ast.Nan(tk.RawToken()) + node.SetPath(ctx.path) + if err := setLineComment(ctx, node, tk); err != nil { + return nil, err + } + return node, nil +} + +func newStringNode(ctx *context, tk *Token) (*ast.StringNode, error) { + node := ast.String(tk.RawToken()) + node.SetPath(ctx.path) + if err := setLineComment(ctx, node, tk); err != nil { + return nil, err + } + return node, nil +} + +func newLiteralNode(ctx *context, tk *Token) (*ast.LiteralNode, error) { + node := ast.Literal(tk.RawToken()) + node.SetPath(ctx.path) + if err := setLineComment(ctx, node, tk); err != nil { + return nil, err + } + return node, nil +} + +func newTagNode(ctx *context, tk *Token) (*ast.TagNode, error) { + node := ast.Tag(tk.RawToken()) + node.SetPath(ctx.path) + if err := setLineComment(ctx, node, tk); err != nil { + return nil, err + } + return node, nil +} + +func newSequenceNode(ctx *context, tk *Token, isFlow bool) (*ast.SequenceNode, error) { + node := ast.Sequence(tk.RawToken(), isFlow) + node.SetPath(ctx.path) + if err := setLineComment(ctx, node, tk); err != nil { + return nil, err + } + return node, nil +} + +func newTagDefaultScalarValueNode(ctx *context, tag *token.Token) (ast.ScalarNode, error) { + pos := *(tag.Position) + pos.Column++ + + var ( + tk *Token + node ast.ScalarNode + ) + switch token.ReservedTagKeyword(tag.Value) { + case token.IntegerTag: + tk = &Token{Token: token.New("0", "0", &pos)} + n, err := newIntegerNode(ctx, tk) + if err != nil { + return nil, err + } + node = n + case token.FloatTag: + tk = &Token{Token: token.New("0", "0", &pos)} + n, err := newFloatNode(ctx, tk) + if err != nil { + return nil, err + } + node = n + case token.StringTag, token.BinaryTag, token.TimestampTag: + tk = &Token{Token: token.New("", "", &pos)} + n, err := newStringNode(ctx, tk) + if err != nil { + return nil, err + } + node = n + case token.BooleanTag: + tk = &Token{Token: token.New("false", "false", &pos)} + n, err := newBoolNode(ctx, tk) + if err != nil { + return nil, err + } + node = n + case token.NullTag: + tk = &Token{Token: token.New("null", "null", &pos)} + n, err := newNullNode(ctx, tk) + if err != nil { + return nil, err + } + node = n + default: + return nil, errors.ErrSyntax(fmt.Sprintf("cannot assign default value for %q tag", tag.Value), tag) + } + ctx.insertToken(tk) + ctx.goNext() + return node, nil +} + +func setLineComment(ctx *context, node ast.Node, tk *Token) error { + if tk == nil || tk.LineComment == nil { + return nil + } + comment := ast.CommentGroup([]*token.Token{tk.LineComment}) + comment.SetPath(ctx.path) + if err := node.SetComment(comment); err != nil { + return err + } + return nil +} + +func setHeadComment(cm *ast.CommentGroupNode, value ast.Node) error { + if cm == nil { + return nil + } + switch n := value.(type) { + case *ast.MappingNode: + if len(n.Values) != 0 && value.GetComment() == nil { + cm.SetPath(n.Values[0].GetPath()) + return n.Values[0].SetComment(cm) + } + case *ast.MappingValueNode: + cm.SetPath(n.GetPath()) + return n.SetComment(cm) + } + cm.SetPath(value.GetPath()) + return value.SetComment(cm) +} diff --git a/vendor/github.com/goccy/go-yaml/parser/option.go b/vendor/github.com/goccy/go-yaml/parser/option.go new file mode 100644 index 000000000..3121a64ac --- /dev/null +++ b/vendor/github.com/goccy/go-yaml/parser/option.go @@ -0,0 +1,12 @@ +package parser + +// Option represents parser's option. +type Option func(p *parser) + +// AllowDuplicateMapKey allow the use of keys with the same name in the same map, +// but by default, this is not permitted. +func AllowDuplicateMapKey() Option { + return func(p *parser) { + p.allowDuplicateMapKey = true + } +} diff --git a/vendor/github.com/goccy/go-yaml/parser/parser.go b/vendor/github.com/goccy/go-yaml/parser/parser.go index 2bec5fea0..2c79d3690 100644 --- a/vendor/github.com/goccy/go-yaml/parser/parser.go +++ b/vendor/github.com/goccy/go-yaml/parser/parser.go @@ -9,699 +9,8 @@ import ( "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{} - -func (p *parser) parseMapping(ctx *context) (*ast.MappingNode, error) { - mapTk := ctx.currentToken() - node := ast.Mapping(mapTk, true) - node.SetPath(ctx.path) - ctx.progress(1) // skip MappingStart token - for ctx.next() { - tk := ctx.currentToken() - if tk.Type == token.MappingEndType { - node.End = tk - return node, nil - } else if tk.Type == token.CollectEntryType { - ctx.progress(1) - continue - } - - value, err := p.parseMappingValue(ctx) - if err != nil { - return nil, errors.Wrapf(err, "failed to parse mapping value in mapping node") - } - mvnode, ok := value.(*ast.MappingValueNode) - if !ok { - return nil, errors.ErrSyntax("failed to parse flow mapping node", value.GetToken()) - } - node.Values = append(node.Values, mvnode) - ctx.progress(1) - } - return nil, errors.ErrSyntax("unterminated flow mapping", node.GetToken()) -} - -func (p *parser) parseSequence(ctx *context) (*ast.SequenceNode, error) { - node := ast.Sequence(ctx.currentToken(), true) - node.SetPath(ctx.path) - ctx.progress(1) // skip SequenceStart token - for ctx.next() { - tk := ctx.currentToken() - if tk.Type == token.SequenceEndType { - node.End = tk - break - } else if tk.Type == token.CollectEntryType { - ctx.progress(1) - continue - } - - value, err := p.parseToken(ctx.withIndex(uint(len(node.Values))), tk) - if err != nil { - return nil, errors.Wrapf(err, "failed to parse sequence value in flow sequence node") - } - node.Values = append(node.Values, value) - ctx.progress(1) - } - return node, nil -} - -func (p *parser) parseTag(ctx *context) (*ast.TagNode, error) { - tagToken := ctx.currentToken() - node := ast.Tag(tagToken) - node.SetPath(ctx.path) - ctx.progress(1) // skip tag token - var ( - value ast.Node - err error - ) - switch token.ReservedTagKeyword(tagToken.Value) { - case token.MappingTag, - token.OrderedMapTag: - value, err = p.parseMapping(ctx) - case token.IntegerTag, - token.FloatTag, - token.StringTag, - token.BinaryTag, - token.TimestampTag, - token.NullTag: - typ := ctx.currentToken().Type - if typ == token.LiteralType || typ == token.FoldedType { - value, err = p.parseLiteral(ctx) - } else { - value = p.parseScalarValue(ctx.currentToken()) - } - case token.SequenceTag, - token.SetTag: - err = errors.ErrSyntax(fmt.Sprintf("sorry, currently not supported %s tag", tagToken.Value), tagToken) - default: - // custom tag - value, err = p.parseToken(ctx, ctx.currentToken()) - } - if err != nil { - return nil, errors.Wrapf(err, "failed to parse tag value") - } - node.Value = value - return node, nil -} - -func (p *parser) removeLeftSideNewLineCharacter(src string) string { - // CR or LF or CRLF - return strings.TrimLeft(strings.TrimLeft(strings.TrimLeft(src, "\r"), "\n"), "\r\n") -} - -func (p *parser) existsNewLineCharacter(src string) bool { - if strings.Index(src, "\n") > 0 { - return true - } - if strings.Index(src, "\r") > 0 { - return true - } - return false -} - -func (p *parser) validateMapKey(tk *token.Token) error { - if tk.Type != token.StringType { - return nil - } - origin := p.removeLeftSideNewLineCharacter(tk.Origin) - if p.existsNewLineCharacter(origin) { - return errors.ErrSyntax("unexpected key name", tk) - } - return nil -} - -func (p *parser) createNullToken(base *token.Token) *token.Token { - pos := *(base.Position) - pos.Column++ - return token.New("null", "null", &pos) -} - -func (p *parser) parseMapValue(ctx *context, key ast.MapKeyNode, colonToken *token.Token) (ast.Node, error) { - node, err := p.createMapValueNode(ctx, key, colonToken) - if err != nil { - return nil, errors.Wrapf(err, "failed to create map value node") - } - if node != nil && node.GetPath() == "" { - node.SetPath(ctx.path) - } - return node, nil -} - -func (p *parser) createMapValueNode(ctx *context, key ast.MapKeyNode, colonToken *token.Token) (ast.Node, error) { - tk := ctx.currentToken() - if tk == nil { - nullToken := p.createNullToken(colonToken) - ctx.insertToken(ctx.idx, nullToken) - return ast.Null(nullToken), nil - } - var comment *ast.CommentGroupNode - if tk.Type == token.CommentType { - comment = p.parseCommentOnly(ctx) - if comment != nil { - comment.SetPath(ctx.withChild(key.GetToken().Value).path) - } - tk = ctx.currentToken() - } - if tk.Position.Column == key.GetToken().Position.Column && tk.Type == token.StringType { - // in this case, - // ---- - // key: - // next - - nullToken := p.createNullToken(colonToken) - ctx.insertToken(ctx.idx, nullToken) - nullNode := ast.Null(nullToken) - - if comment != nil { - 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. - keyComment := key.GetComment() - if keyComment != nil { - if err := key.SetComment(nil); err != nil { - return nil, err - } - nullNode.SetComment(keyComment) - } - } - return nullNode, nil - } - - if tk.Position.Column < key.GetToken().Position.Column { - // in this case, - // ---- - // key: - // next - nullToken := p.createNullToken(colonToken) - ctx.insertToken(ctx.idx, nullToken) - nullNode := ast.Null(nullToken) - if comment != nil { - nullNode.SetComment(comment) - } - return nullNode, nil - } - - value, err := p.parseToken(ctx, ctx.currentToken()) - if err != nil { - return nil, errors.Wrapf(err, "failed to parse mapping 'value' node") - } - if comment != nil { - value.SetComment(comment) - } - return value, nil -} - -func (p *parser) validateMapValue(ctx *context, key, value ast.Node) error { - keyColumn := key.GetToken().Position.Column - valueColumn := value.GetToken().Position.Column - if keyColumn != valueColumn { - return nil - } - if value.Type() != ast.StringType { - return nil - } - ntk := ctx.nextToken() - if ntk == nil || (ntk.Type != token.MappingValueType && ntk.Type != token.SequenceEntryType) { - return errors.ErrSyntax("could not found expected ':' token", value.GetToken()) - } - return nil -} - -func (p *parser) parseMappingValue(ctx *context) (ast.Node, error) { - key, err := p.parseMapKey(ctx) - if err != nil { - return nil, errors.Wrapf(err, "failed to parse map key") - } - keyText := key.GetToken().Value - key.SetPath(ctx.withChild(keyText).path) - if err := p.validateMapKey(key.GetToken()); err != nil { - return nil, errors.Wrapf(err, "validate mapping key error") - } - ctx.progress(1) // progress to mapping value token - tk := ctx.currentToken() // get mapping value token - if tk == nil { - return nil, errors.ErrSyntax("unexpected map", key.GetToken()) - } - ctx.progress(1) // progress to value token - if err := p.setSameLineCommentIfExists(ctx.withChild(keyText), key); err != nil { - return nil, errors.Wrapf(err, "failed to set same line comment to node") - } - if key.GetComment() != nil { - // if current token is comment, GetComment() is not nil. - // then progress to value token - ctx.progressIgnoreComment(1) - } - - value, err := p.parseMapValue(ctx.withChild(keyText), key, tk) - if err != nil { - return nil, errors.Wrapf(err, "failed to parse map value") - } - if err := p.validateMapValue(ctx, key, value); err != nil { - return nil, errors.Wrapf(err, "failed to validate map value") - } - - mvnode := ast.MappingValue(tk, key, value) - mvnode.SetPath(ctx.withChild(keyText).path) - node := ast.Mapping(tk, false, mvnode) - node.SetPath(ctx.withChild(keyText).path) - - ntk := ctx.nextNotCommentToken() - antk := ctx.afterNextNotCommentToken() - for antk != nil && antk.Type == token.MappingValueType && - ntk.Position.Column == key.GetToken().Position.Column { - ctx.progressIgnoreComment(1) - value, err := p.parseToken(ctx, ctx.currentToken()) - if err != nil { - return nil, errors.Wrapf(err, "failed to parse mapping node") - } - switch value.Type() { - case ast.MappingType: - c := value.(*ast.MappingNode) - comment := c.GetComment() - for idx, v := range c.Values { - if idx == 0 && comment != nil { - if err := v.SetComment(comment); err != nil { - return nil, errors.Wrapf(err, "failed to set comment token to node") - } - } - node.Values = append(node.Values, v) - } - case ast.MappingValueType: - node.Values = append(node.Values, value.(*ast.MappingValueNode)) - default: - return nil, xerrors.Errorf("failed to parse mapping value node node is %s", value.Type()) - } - ntk = ctx.nextNotCommentToken() - antk = ctx.afterNextNotCommentToken() - } - if len(node.Values) == 1 { - mapKeyCol := mvnode.Key.GetToken().Position.Column - commentTk := ctx.nextToken() - if commentTk != nil && commentTk.Type == token.CommentType && mapKeyCol <= commentTk.Position.Column { - // If the comment is in the same or deeper column as the last element column in map value, - // treat it as a footer comment for the last element. - comment := p.parseFootComment(ctx, mapKeyCol) - mvnode.FootComment = comment - } - return mvnode, nil - } - mapCol := node.GetToken().Position.Column - commentTk := ctx.nextToken() - if commentTk != nil && commentTk.Type == token.CommentType && mapCol <= commentTk.Position.Column { - // If the comment is in the same or deeper column as the last element column in map value, - // treat it as a footer comment for the last element. - comment := p.parseFootComment(ctx, mapCol) - node.FootComment = comment - } - return node, nil -} - -func (p *parser) parseSequenceEntry(ctx *context) (*ast.SequenceNode, error) { - tk := ctx.currentToken() - sequenceNode := ast.Sequence(tk, false) - sequenceNode.SetPath(ctx.path) - curColumn := tk.Position.Column - for tk.Type == token.SequenceEntryType { - ctx.progress(1) // skip sequence token - tk = ctx.currentToken() - if tk == nil { - return nil, errors.ErrSyntax("empty sequence entry", ctx.previousToken()) - } - var comment *ast.CommentGroupNode - if tk.Type == token.CommentType { - comment = p.parseCommentOnly(ctx) - tk = ctx.currentToken() - if tk.Type == token.SequenceEntryType { - ctx.progress(1) // skip sequence token - } - } - value, err := p.parseToken(ctx.withIndex(uint(len(sequenceNode.Values))), ctx.currentToken()) - if err != nil { - return nil, errors.Wrapf(err, "failed to parse sequence") - } - if comment != nil { - comment.SetPath(ctx.withIndex(uint(len(sequenceNode.Values))).path) - sequenceNode.ValueHeadComments = append(sequenceNode.ValueHeadComments, comment) - } else { - sequenceNode.ValueHeadComments = append(sequenceNode.ValueHeadComments, nil) - } - sequenceNode.Values = append(sequenceNode.Values, value) - tk = ctx.nextNotCommentToken() - if tk == nil { - break - } - if tk.Type != token.SequenceEntryType { - break - } - if tk.Position.Column != curColumn { - break - } - ctx.progressIgnoreComment(1) - } - commentTk := ctx.nextToken() - if commentTk != nil && commentTk.Type == token.CommentType && curColumn <= commentTk.Position.Column { - // If the comment is in the same or deeper column as the last element column in sequence value, - // treat it as a footer comment for the last element. - comment := p.parseFootComment(ctx, curColumn) - sequenceNode.FootComment = comment - } - return sequenceNode, nil -} - -func (p *parser) parseAnchor(ctx *context) (*ast.AnchorNode, error) { - tk := ctx.currentToken() - anchor := ast.Anchor(tk) - anchor.SetPath(ctx.path) - ntk := ctx.nextToken() - if ntk == nil { - return nil, errors.ErrSyntax("unexpected anchor. anchor name is undefined", tk) - } - ctx.progress(1) // skip anchor token - name, err := p.parseToken(ctx, ctx.currentToken()) - if err != nil { - return nil, errors.Wrapf(err, "failed to parser anchor name node") - } - anchor.Name = name - ntk = ctx.nextToken() - if ntk == nil { - return nil, errors.ErrSyntax("unexpected anchor. anchor value is undefined", ctx.currentToken()) - } - ctx.progress(1) - value, err := p.parseToken(ctx, ctx.currentToken()) - if err != nil { - return nil, errors.Wrapf(err, "failed to parser anchor name node") - } - anchor.Value = value - return anchor, nil -} - -func (p *parser) parseAlias(ctx *context) (*ast.AliasNode, error) { - tk := ctx.currentToken() - alias := ast.Alias(tk) - alias.SetPath(ctx.path) - ntk := ctx.nextToken() - if ntk == nil { - return nil, errors.ErrSyntax("unexpected alias. alias name is undefined", tk) - } - ctx.progress(1) // skip alias token - name, err := p.parseToken(ctx, ctx.currentToken()) - if err != nil { - return nil, errors.Wrapf(err, "failed to parser alias name node") - } - alias.Value = name - return alias, nil -} - -func (p *parser) parseMapKey(ctx *context) (ast.MapKeyNode, error) { - tk := ctx.currentToken() - if value := p.parseScalarValue(tk); value != nil { - return value, nil - } - switch tk.Type { - case token.MergeKeyType: - return ast.MergeKey(tk), nil - case token.MappingKeyType: - return p.parseMappingKey(ctx) - } - return nil, errors.ErrSyntax("unexpected mapping key", tk) -} - -func (p *parser) parseStringValue(tk *token.Token) *ast.StringNode { - switch tk.Type { - case token.StringType, - token.SingleQuoteType, - token.DoubleQuoteType: - return ast.String(tk) - } - return nil -} - -func (p *parser) parseScalarValueWithComment(ctx *context, tk *token.Token) (ast.ScalarNode, error) { - node := p.parseScalarValue(tk) - if node == nil { - return nil, nil - } - node.SetPath(ctx.path) - if p.isSameLineComment(ctx.nextToken(), node) { - ctx.progress(1) - if err := p.setSameLineCommentIfExists(ctx, node); err != nil { - return nil, errors.Wrapf(err, "failed to set same line comment to node") - } - } - return node, nil -} - -func (p *parser) parseScalarValue(tk *token.Token) ast.ScalarNode { - if node := p.parseStringValue(tk); node != nil { - return node - } - switch tk.Type { - case token.NullType: - return ast.Null(tk) - case token.BoolType: - return ast.Bool(tk) - case token.IntegerType, - token.BinaryIntegerType, - token.OctetIntegerType, - token.HexIntegerType: - return ast.Integer(tk) - case token.FloatType: - return ast.Float(tk) - case token.InfinityType: - return ast.Infinity(tk) - case token.NanType: - return ast.Nan(tk) - } - return nil -} - -func (p *parser) parseDirective(ctx *context) (*ast.DirectiveNode, error) { - node := ast.Directive(ctx.currentToken()) - ctx.progress(1) // skip directive token - value, err := p.parseToken(ctx, ctx.currentToken()) - if err != nil { - return nil, errors.Wrapf(err, "failed to parse directive value") - } - node.Value = value - ctx.progress(1) - tk := ctx.currentToken() - if tk == nil { - // Since current token is nil, use the previous token to specify - // the syntax error location. - return nil, errors.ErrSyntax("unexpected directive value. document not started", ctx.previousToken()) - } - if tk.Type != token.DocumentHeaderType { - return nil, errors.ErrSyntax("unexpected directive value. document not started", ctx.currentToken()) - } - return node, nil -} - -func (p *parser) parseLiteral(ctx *context) (*ast.LiteralNode, error) { - node := ast.Literal(ctx.currentToken()) - ctx.progress(1) // skip literal/folded token - - tk := ctx.currentToken() - var comment *ast.CommentGroupNode - if tk.Type == token.CommentType { - comment = p.parseCommentOnly(ctx) - comment.SetPath(ctx.path) - if err := node.SetComment(comment); err != nil { - return nil, errors.Wrapf(err, "failed to set comment to literal") - } - tk = ctx.currentToken() - } - value, err := p.parseToken(ctx, tk) - if err != nil { - return nil, errors.Wrapf(err, "failed to parse literal/folded value") - } - snode, ok := value.(*ast.StringNode) - if !ok { - return nil, errors.ErrSyntax("unexpected token. required string token", value.GetToken()) - } - node.Value = snode - return node, nil -} - -func (p *parser) isSameLineComment(tk *token.Token, node ast.Node) bool { - if tk == nil { - return false - } - if tk.Type != token.CommentType { - return false - } - return tk.Position.Line == node.GetToken().Position.Line -} - -func (p *parser) setSameLineCommentIfExists(ctx *context, node ast.Node) error { - tk := ctx.currentToken() - if !p.isSameLineComment(tk, node) { - return nil - } - comment := ast.CommentGroup([]*token.Token{tk}) - comment.SetPath(ctx.path) - if err := node.SetComment(comment); err != nil { - return errors.Wrapf(err, "failed to set comment token to ast.Node") - } - return nil -} - -func (p *parser) parseDocument(ctx *context) (*ast.DocumentNode, error) { - startTk := ctx.currentToken() - ctx.progress(1) // skip document header token - body, err := p.parseToken(ctx, ctx.currentToken()) - if err != nil { - return nil, errors.Wrapf(err, "failed to parse document body") - } - node := ast.Document(startTk, body) - if ntk := ctx.nextToken(); ntk != nil && ntk.Type == token.DocumentEndType { - node.End = ntk - ctx.progress(1) - } - return node, nil -} - -func (p *parser) parseCommentOnly(ctx *context) *ast.CommentGroupNode { - commentTokens := []*token.Token{} - for { - tk := ctx.currentToken() - if tk == nil { - break - } - if tk.Type != token.CommentType { - break - } - commentTokens = append(commentTokens, tk) - ctx.progressIgnoreComment(1) // skip comment token - } - return ast.CommentGroup(commentTokens) -} - -func (p *parser) parseFootComment(ctx *context, col int) *ast.CommentGroupNode { - commentTokens := []*token.Token{} - for { - ctx.progressIgnoreComment(1) - commentTokens = append(commentTokens, ctx.currentToken()) - - nextTk := ctx.nextToken() - if nextTk == nil { - break - } - if nextTk.Type != token.CommentType { - break - } - if col > nextTk.Position.Column { - break - } - } - return ast.CommentGroup(commentTokens) -} - -func (p *parser) parseComment(ctx *context) (ast.Node, error) { - group := p.parseCommentOnly(ctx) - node, err := p.parseToken(ctx, ctx.currentToken()) - if err != nil { - return nil, errors.Wrapf(err, "failed to parse node after comment") - } - if node == nil { - return group, nil - } - group.SetPath(node.GetPath()) - if err := node.SetComment(group); err != nil { - return nil, errors.Wrapf(err, "failed to set comment token to node") - } - return node, nil -} - -func (p *parser) parseMappingKey(ctx *context) (*ast.MappingKeyNode, error) { - keyTk := ctx.currentToken() - node := ast.MappingKey(keyTk) - node.SetPath(ctx.path) - ctx.progress(1) // skip mapping key token - value, err := p.parseToken(ctx.withChild(keyTk.Value), ctx.currentToken()) - if err != nil { - return nil, errors.Wrapf(err, "failed to parse map key") - } - node.Value = value - return node, nil -} - -func (p *parser) parseToken(ctx *context, tk *token.Token) (ast.Node, error) { - node, err := p.createNodeFromToken(ctx, tk) - if err != nil { - return nil, errors.Wrapf(err, "failed to create node from token") - } - if node != nil && node.GetPath() == "" { - node.SetPath(ctx.path) - } - return node, nil -} - -func (p *parser) createNodeFromToken(ctx *context, tk *token.Token) (ast.Node, error) { - if tk == nil { - return nil, nil - } - if tk.NextType() == token.MappingValueType { - node, err := p.parseMappingValue(ctx) - return node, err - } - node, err := p.parseScalarValueWithComment(ctx, tk) - if err != nil { - return nil, errors.Wrapf(err, "failed to parse scalar value") - } - if node != nil { - return node, nil - } - switch tk.Type { - case token.CommentType: - return p.parseComment(ctx) - case token.MappingKeyType: - return p.parseMappingKey(ctx) - case token.DocumentHeaderType: - return p.parseDocument(ctx) - case token.MappingStartType: - return p.parseMapping(ctx) - case token.SequenceStartType: - return p.parseSequence(ctx) - case token.SequenceEntryType: - return p.parseSequenceEntry(ctx) - case token.AnchorType: - return p.parseAnchor(ctx) - case token.AliasType: - return p.parseAlias(ctx) - case token.DirectiveType: - return p.parseDirective(ctx) - case token.TagType: - return p.parseTag(ctx) - case token.LiteralType, token.FoldedType: - return p.parseLiteral(ctx) - } - return nil, nil -} - -func (p *parser) parse(tokens token.Tokens, mode Mode) (*ast.File, error) { - ctx := newContext(tokens, mode) - file := &ast.File{Docs: []*ast.DocumentNode{}} - for ctx.next() { - node, err := p.parseToken(ctx, ctx.currentToken()) - if err != nil { - return nil, errors.Wrapf(err, "failed to parse") - } - ctx.progressIgnoreComment(1) - if node == nil { - continue - } - if doc, ok := node.(*ast.DocumentNode); ok { - file.Docs = append(file.Docs, doc) - } else { - file.Docs = append(file.Docs, ast.Document(nil, node)) - } - } - return file, nil -} - type Mode uint const ( @@ -709,35 +18,1303 @@ const ( ) // ParseBytes parse from byte slice, and returns ast.File -func ParseBytes(bytes []byte, mode Mode) (*ast.File, error) { +func ParseBytes(bytes []byte, mode Mode, opts ...Option) (*ast.File, error) { tokens := lexer.Tokenize(string(bytes)) - f, err := Parse(tokens, mode) + f, err := Parse(tokens, mode, opts...) if err != nil { - return nil, errors.Wrapf(err, "failed to parse") + return nil, err } return f, nil } // Parse parse from token instances, and returns ast.File -func Parse(tokens token.Tokens, mode Mode) (*ast.File, error) { - var p parser - f, err := p.parse(tokens, mode) +func Parse(tokens token.Tokens, mode Mode, opts ...Option) (*ast.File, error) { + if tk := tokens.InvalidToken(); tk != nil { + return nil, errors.ErrSyntax(tk.Error, tk) + } + p, err := newParser(tokens, mode, opts) if err != nil { - return nil, errors.Wrapf(err, "failed to parse") + return nil, err + } + f, err := p.parse(newContext()) + if err != nil { + return nil, err } return f, nil } // Parse parse from filename, and returns ast.File -func ParseFile(filename string, mode Mode) (*ast.File, error) { +func ParseFile(filename string, mode Mode, opts ...Option) (*ast.File, error) { file, err := os.ReadFile(filename) if err != nil { - return nil, errors.Wrapf(err, "failed to read file: %s", filename) + return nil, err } - f, err := ParseBytes(file, mode) + f, err := ParseBytes(file, mode, opts...) if err != nil { - return nil, errors.Wrapf(err, "failed to parse") + return nil, err } f.Name = filename return f, nil } + +type YAMLVersion string + +const ( + YAML10 YAMLVersion = "1.0" + YAML11 YAMLVersion = "1.1" + YAML12 YAMLVersion = "1.2" + YAML13 YAMLVersion = "1.3" +) + +var yamlVersionMap = map[string]YAMLVersion{ + "1.0": YAML10, + "1.1": YAML11, + "1.2": YAML12, + "1.3": YAML13, +} + +type parser struct { + tokens []*Token + pathMap map[string]ast.Node + yamlVersion YAMLVersion + allowDuplicateMapKey bool + secondaryTagDirective *ast.DirectiveNode +} + +func newParser(tokens token.Tokens, mode Mode, opts []Option) (*parser, error) { + filteredTokens := []*token.Token{} + if mode&ParseComments != 0 { + filteredTokens = tokens + } else { + for _, tk := range tokens { + if tk.Type == token.CommentType { + continue + } + // keep prev/next reference between tokens containing comments + // https://github.com/goccy/go-yaml/issues/254 + filteredTokens = append(filteredTokens, tk) + } + } + tks, err := CreateGroupedTokens(token.Tokens(filteredTokens)) + if err != nil { + return nil, err + } + p := &parser{ + tokens: tks, + pathMap: make(map[string]ast.Node), + } + for _, opt := range opts { + opt(p) + } + return p, nil +} + +func (p *parser) parse(ctx *context) (*ast.File, error) { + file := &ast.File{Docs: []*ast.DocumentNode{}} + for _, token := range p.tokens { + doc, err := p.parseDocument(ctx, token.Group) + if err != nil { + return nil, err + } + file.Docs = append(file.Docs, doc) + } + return file, nil +} + +func (p *parser) parseDocument(ctx *context, docGroup *TokenGroup) (*ast.DocumentNode, error) { + if len(docGroup.Tokens) == 0 { + return ast.Document(docGroup.RawToken(), nil), nil + } + + p.pathMap = make(map[string]ast.Node) + + var ( + tokens = docGroup.Tokens + start *token.Token + end *token.Token + ) + if docGroup.First().Type() == token.DocumentHeaderType { + start = docGroup.First().RawToken() + tokens = tokens[1:] + } + if docGroup.Last().Type() == token.DocumentEndType { + end = docGroup.Last().RawToken() + tokens = tokens[:len(tokens)-1] + defer func() { + // clear yaml version value if DocumentEnd token (...) is specified. + p.yamlVersion = "" + }() + } + + if len(tokens) == 0 { + return ast.Document(docGroup.RawToken(), nil), nil + } + + body, err := p.parseDocumentBody(ctx.withGroup(&TokenGroup{ + Type: TokenGroupDocumentBody, + Tokens: tokens, + })) + if err != nil { + return nil, err + } + node := ast.Document(start, body) + node.End = end + return node, nil +} + +func (p *parser) parseDocumentBody(ctx *context) (ast.Node, error) { + node, err := p.parseToken(ctx, ctx.currentToken()) + if err != nil { + return nil, err + } + if ctx.next() { + return nil, errors.ErrSyntax("value is not allowed in this context", ctx.currentToken().RawToken()) + } + return node, nil +} + +func (p *parser) parseToken(ctx *context, tk *Token) (ast.Node, error) { + switch tk.GroupType() { + case TokenGroupMapKey, TokenGroupMapKeyValue: + return p.parseMap(ctx) + case TokenGroupDirective: + node, err := p.parseDirective(ctx.withGroup(tk.Group), tk.Group) + if err != nil { + return nil, err + } + ctx.goNext() + return node, nil + case TokenGroupDirectiveName: + node, err := p.parseDirectiveName(ctx.withGroup(tk.Group)) + if err != nil { + return nil, err + } + ctx.goNext() + return node, nil + case TokenGroupAnchor: + node, err := p.parseAnchor(ctx.withGroup(tk.Group), tk.Group) + if err != nil { + return nil, err + } + ctx.goNext() + return node, nil + case TokenGroupAnchorName: + anchor, err := p.parseAnchorName(ctx.withGroup(tk.Group)) + if err != nil { + return nil, err + } + ctx.goNext() + if ctx.isTokenNotFound() { + return nil, errors.ErrSyntax("could not find anchor value", tk.RawToken()) + } + value, err := p.parseToken(ctx, ctx.currentToken()) + if err != nil { + return nil, err + } + if _, ok := value.(*ast.AnchorNode); ok { + return nil, errors.ErrSyntax("anchors cannot be used consecutively", value.GetToken()) + } + anchor.Value = value + return anchor, nil + case TokenGroupAlias: + node, err := p.parseAlias(ctx.withGroup(tk.Group)) + if err != nil { + return nil, err + } + ctx.goNext() + return node, nil + case TokenGroupLiteral, TokenGroupFolded: + node, err := p.parseLiteral(ctx.withGroup(tk.Group)) + if err != nil { + return nil, err + } + ctx.goNext() + return node, nil + case TokenGroupScalarTag: + node, err := p.parseTag(ctx.withGroup(tk.Group)) + if err != nil { + return nil, err + } + ctx.goNext() + return node, nil + } + switch tk.Type() { + case token.CommentType: + return p.parseComment(ctx) + case token.TagType: + return p.parseTag(ctx) + case token.MappingStartType: + return p.parseFlowMap(ctx.withFlow(true)) + case token.SequenceStartType: + return p.parseFlowSequence(ctx.withFlow(true)) + case token.SequenceEntryType: + return p.parseSequence(ctx) + case token.SequenceEndType: + // SequenceEndType is always validated in parseFlowSequence. + // Therefore, if this is found in other cases, it is treated as a syntax error. + return nil, errors.ErrSyntax("could not find '[' character corresponding to ']'", tk.RawToken()) + case token.MappingEndType: + // MappingEndType is always validated in parseFlowMap. + // Therefore, if this is found in other cases, it is treated as a syntax error. + return nil, errors.ErrSyntax("could not find '{' character corresponding to '}'", tk.RawToken()) + case token.MappingValueType: + return nil, errors.ErrSyntax("found an invalid key for this map", tk.RawToken()) + } + node, err := p.parseScalarValue(ctx, tk) + if err != nil { + return nil, err + } + ctx.goNext() + return node, nil +} + +func (p *parser) parseScalarValue(ctx *context, tk *Token) (ast.ScalarNode, error) { + if tk.Group != nil { + switch tk.GroupType() { + case TokenGroupAnchor: + return p.parseAnchor(ctx.withGroup(tk.Group), tk.Group) + case TokenGroupAnchorName: + anchor, err := p.parseAnchorName(ctx.withGroup(tk.Group)) + if err != nil { + return nil, err + } + ctx.goNext() + if ctx.isTokenNotFound() { + return nil, errors.ErrSyntax("could not find anchor value", tk.RawToken()) + } + value, err := p.parseToken(ctx, ctx.currentToken()) + if err != nil { + return nil, err + } + if _, ok := value.(*ast.AnchorNode); ok { + return nil, errors.ErrSyntax("anchors cannot be used consecutively", value.GetToken()) + } + anchor.Value = value + return anchor, nil + case TokenGroupAlias: + return p.parseAlias(ctx.withGroup(tk.Group)) + case TokenGroupLiteral, TokenGroupFolded: + return p.parseLiteral(ctx.withGroup(tk.Group)) + case TokenGroupScalarTag: + return p.parseTag(ctx.withGroup(tk.Group)) + default: + return nil, errors.ErrSyntax("unexpected scalar value", tk.RawToken()) + } + } + switch tk.Type() { + case token.MergeKeyType: + return newMergeKeyNode(ctx, tk) + case token.NullType, token.ImplicitNullType: + return newNullNode(ctx, tk) + case token.BoolType: + return newBoolNode(ctx, tk) + case token.IntegerType, token.BinaryIntegerType, token.OctetIntegerType, token.HexIntegerType: + return newIntegerNode(ctx, tk) + case token.FloatType: + return newFloatNode(ctx, tk) + case token.InfinityType: + return newInfinityNode(ctx, tk) + case token.NanType: + return newNanNode(ctx, tk) + case token.StringType, token.SingleQuoteType, token.DoubleQuoteType: + return newStringNode(ctx, tk) + case token.TagType: + // this case applies when it is a scalar tag and its value does not exist. + // Examples of cases where the value does not exist include cases like `key: !!str,` or `!!str : value`. + return p.parseScalarTag(ctx) + } + return nil, errors.ErrSyntax("unexpected scalar value type", tk.RawToken()) +} + +func (p *parser) parseFlowMap(ctx *context) (*ast.MappingNode, error) { + node, err := newMappingNode(ctx, ctx.currentToken(), true) + if err != nil { + return nil, err + } + ctx.goNext() // skip MappingStart token + + isFirst := true + for ctx.next() { + tk := ctx.currentToken() + if tk.Type() == token.MappingEndType { + node.End = tk.RawToken() + break + } + + var entryTk *Token + if tk.Type() == token.CollectEntryType { + entryTk = tk + ctx.goNext() + } else if !isFirst { + return nil, errors.ErrSyntax("',' or '}' must be specified", tk.RawToken()) + } + + if tk := ctx.currentToken(); tk.Type() == token.MappingEndType { + // this case is here: "{ elem, }". + // In this case, ignore the last element and break mapping parsing. + node.End = tk.RawToken() + break + } + + mapKeyTk := ctx.currentToken() + switch mapKeyTk.GroupType() { + case TokenGroupMapKeyValue: + value, err := p.parseMapKeyValue(ctx.withGroup(mapKeyTk.Group), mapKeyTk.Group, entryTk) + if err != nil { + return nil, err + } + node.Values = append(node.Values, value) + ctx.goNext() + case TokenGroupMapKey: + key, err := p.parseMapKey(ctx.withGroup(mapKeyTk.Group), mapKeyTk.Group) + if err != nil { + return nil, err + } + ctx := ctx.withChild(p.mapKeyText(key)) + colonTk := mapKeyTk.Group.Last() + if p.isFlowMapDelim(ctx.nextToken()) { + value, err := newNullNode(ctx, ctx.insertNullToken(colonTk)) + if err != nil { + return nil, err + } + mapValue, err := newMappingValueNode(ctx, colonTk, entryTk, key, value) + if err != nil { + return nil, err + } + node.Values = append(node.Values, mapValue) + ctx.goNext() + } else { + ctx.goNext() + if ctx.isTokenNotFound() { + return nil, errors.ErrSyntax("could not find map value", colonTk.RawToken()) + } + value, err := p.parseToken(ctx, ctx.currentToken()) + if err != nil { + return nil, err + } + mapValue, err := newMappingValueNode(ctx, colonTk, entryTk, key, value) + if err != nil { + return nil, err + } + node.Values = append(node.Values, mapValue) + } + default: + if !p.isFlowMapDelim(ctx.nextToken()) { + errTk := mapKeyTk + if errTk == nil { + errTk = tk + } + return nil, errors.ErrSyntax("could not find flow map content", errTk.RawToken()) + } + key, err := p.parseScalarValue(ctx, mapKeyTk) + if err != nil { + return nil, err + } + value, err := newNullNode(ctx, ctx.insertNullToken(mapKeyTk)) + if err != nil { + return nil, err + } + mapValue, err := newMappingValueNode(ctx, mapKeyTk, entryTk, key, value) + if err != nil { + return nil, err + } + node.Values = append(node.Values, mapValue) + ctx.goNext() + } + isFirst = false + } + if node.End == nil { + return nil, errors.ErrSyntax("could not find flow mapping end token '}'", node.Start) + } + ctx.goNext() // skip mapping end token. + return node, nil +} + +func (p *parser) isFlowMapDelim(tk *Token) bool { + return tk.Type() == token.MappingEndType || tk.Type() == token.CollectEntryType +} + +func (p *parser) parseMap(ctx *context) (*ast.MappingNode, error) { + keyTk := ctx.currentToken() + if keyTk.Group == nil { + return nil, errors.ErrSyntax("unexpected map key", keyTk.RawToken()) + } + var keyValueNode *ast.MappingValueNode + if keyTk.GroupType() == TokenGroupMapKeyValue { + node, err := p.parseMapKeyValue(ctx.withGroup(keyTk.Group), keyTk.Group, nil) + if err != nil { + return nil, err + } + keyValueNode = node + ctx.goNext() + if err := p.validateMapKeyValueNextToken(ctx, keyTk, ctx.currentToken()); err != nil { + return nil, err + } + } else { + key, err := p.parseMapKey(ctx.withGroup(keyTk.Group), keyTk.Group) + if err != nil { + return nil, err + } + ctx.goNext() + + valueTk := ctx.currentToken() + if keyTk.Line() == valueTk.Line() && valueTk.Type() == token.SequenceEntryType { + return nil, errors.ErrSyntax("block sequence entries are not allowed in this context", valueTk.RawToken()) + } + ctx := ctx.withChild(p.mapKeyText(key)) + value, err := p.parseMapValue(ctx, key, keyTk.Group.Last()) + if err != nil { + return nil, err + } + node, err := newMappingValueNode(ctx, keyTk.Group.Last(), nil, key, value) + if err != nil { + return nil, err + } + keyValueNode = node + } + mapNode, err := newMappingNode(ctx, &Token{Token: keyValueNode.GetToken()}, false, keyValueNode) + if err != nil { + return nil, err + } + var tk *Token + if ctx.isComment() { + tk = ctx.nextNotCommentToken() + } else { + tk = ctx.currentToken() + } + for tk.Column() == keyTk.Column() { + typ := tk.Type() + if ctx.isFlow && typ == token.SequenceEndType { + // [ + // key: value + // ] <= + break + } + if !p.isMapToken(tk) { + return nil, errors.ErrSyntax("non-map value is specified", tk.RawToken()) + } + cm := p.parseHeadComment(ctx) + if typ == token.MappingEndType { + // a: { + // b: c + // } <= + ctx.goNext() + break + } + node, err := p.parseMap(ctx) + if err != nil { + return nil, err + } + if len(node.Values) != 0 { + if err := setHeadComment(cm, node.Values[0]); err != nil { + return nil, err + } + } + mapNode.Values = append(mapNode.Values, node.Values...) + if node.FootComment != nil { + mapNode.Values[len(mapNode.Values)-1].FootComment = node.FootComment + } + tk = ctx.currentToken() + } + if ctx.isComment() { + if keyTk.Column() <= ctx.currentToken().Column() { + // If the comment is in the same or deeper column as the last element column in map value, + // treat it as a footer comment for the last element. + if len(mapNode.Values) == 1 { + mapNode.Values[0].FootComment = p.parseFootComment(ctx, keyTk.Column()) + mapNode.Values[0].FootComment.SetPath(mapNode.Values[0].Key.GetPath()) + } else { + mapNode.FootComment = p.parseFootComment(ctx, keyTk.Column()) + mapNode.FootComment.SetPath(mapNode.GetPath()) + } + } + } + return mapNode, nil +} + +func (p *parser) validateMapKeyValueNextToken(ctx *context, keyTk, tk *Token) error { + if tk == nil { + return nil + } + if tk.Column() <= keyTk.Column() { + return nil + } + if ctx.isComment() { + return nil + } + if ctx.isFlow && (tk.Type() == token.CollectEntryType || tk.Type() == token.SequenceEndType) { + return nil + } + // a: b + // c <= this token is invalid. + return errors.ErrSyntax("value is not allowed in this context. map key-value is pre-defined", tk.RawToken()) +} + +func (p *parser) isMapToken(tk *Token) bool { + if tk.Group == nil { + return tk.Type() == token.MappingStartType || tk.Type() == token.MappingEndType + } + g := tk.Group + return g.Type == TokenGroupMapKey || g.Type == TokenGroupMapKeyValue +} + +func (p *parser) parseMapKeyValue(ctx *context, g *TokenGroup, entryTk *Token) (*ast.MappingValueNode, error) { + if g.Type != TokenGroupMapKeyValue { + return nil, errors.ErrSyntax("unexpected map key-value pair", g.RawToken()) + } + if g.First().Group == nil { + return nil, errors.ErrSyntax("unexpected map key", g.RawToken()) + } + keyGroup := g.First().Group + key, err := p.parseMapKey(ctx.withGroup(keyGroup), keyGroup) + if err != nil { + return nil, err + } + + c := ctx.withChild(p.mapKeyText(key)) + value, err := p.parseToken(c, g.Last()) + if err != nil { + return nil, err + } + return newMappingValueNode(c, keyGroup.Last(), entryTk, key, value) +} + +func (p *parser) parseMapKey(ctx *context, g *TokenGroup) (ast.MapKeyNode, error) { + if g.Type != TokenGroupMapKey { + return nil, errors.ErrSyntax("unexpected map key", g.RawToken()) + } + if g.First().Type() == token.MappingKeyType { + mapKeyTk := g.First() + if mapKeyTk.Group != nil { + ctx = ctx.withGroup(mapKeyTk.Group) + } + key, err := newMappingKeyNode(ctx, mapKeyTk) + if err != nil { + return nil, err + } + ctx.goNext() // skip mapping key token + if ctx.isTokenNotFound() { + return nil, errors.ErrSyntax("could not find value for mapping key", mapKeyTk.RawToken()) + } + + scalar, err := p.parseScalarValue(ctx, ctx.currentToken()) + if err != nil { + return nil, err + } + key.Value = scalar + keyText := p.mapKeyText(scalar) + keyPath := ctx.withChild(keyText).path + key.SetPath(keyPath) + if err := p.validateMapKey(ctx, key.GetToken(), keyPath, g.Last()); err != nil { + return nil, err + } + p.pathMap[keyPath] = key + return key, nil + } + if g.Last().Type() != token.MappingValueType { + return nil, errors.ErrSyntax("expected map key-value delimiter ':'", g.Last().RawToken()) + } + + scalar, err := p.parseScalarValue(ctx, g.First()) + if err != nil { + return nil, err + } + key, ok := scalar.(ast.MapKeyNode) + if !ok { + return nil, errors.ErrSyntax("cannot take map-key node", scalar.GetToken()) + } + keyText := p.mapKeyText(key) + keyPath := ctx.withChild(keyText).path + key.SetPath(keyPath) + if err := p.validateMapKey(ctx, key.GetToken(), keyPath, g.Last()); err != nil { + return nil, err + } + p.pathMap[keyPath] = key + return key, nil +} + +func (p *parser) validateMapKey(ctx *context, tk *token.Token, keyPath string, colonTk *Token) error { + if !p.allowDuplicateMapKey { + if n, exists := p.pathMap[keyPath]; exists { + pos := n.GetToken().Position + return errors.ErrSyntax( + fmt.Sprintf("mapping key %q already defined at [%d:%d]", tk.Value, pos.Line, pos.Column), + tk, + ) + } + } + origin := p.removeLeftWhiteSpace(tk.Origin) + if ctx.isFlow { + if tk.Type == token.StringType { + origin = p.removeRightWhiteSpace(origin) + if tk.Position.Line+p.newLineCharacterNum(origin) != colonTk.Line() { + return errors.ErrSyntax("map key definition includes an implicit line break", tk) + } + } + return nil + } + if tk.Type != token.StringType && tk.Type != token.SingleQuoteType && tk.Type != token.DoubleQuoteType { + return nil + } + if p.existsNewLineCharacter(origin) { + return errors.ErrSyntax("unexpected key name", tk) + } + return nil +} + +func (p *parser) removeLeftWhiteSpace(src string) string { + // CR or LF or CRLF + return strings.TrimLeftFunc(src, func(r rune) bool { + return r == ' ' || r == '\r' || r == '\n' + }) +} + +func (p *parser) removeRightWhiteSpace(src string) string { + // CR or LF or CRLF + return strings.TrimRightFunc(src, func(r rune) bool { + return r == ' ' || r == '\r' || r == '\n' + }) +} + +func (p *parser) existsNewLineCharacter(src string) bool { + return p.newLineCharacterNum(src) > 0 +} + +func (p *parser) newLineCharacterNum(src string) int { + var num int + for i := 0; i < len(src); i++ { + switch src[i] { + case '\r': + if len(src) > i+1 && src[i+1] == '\n' { + i++ + } + num++ + case '\n': + num++ + } + } + return num +} + +func (p *parser) mapKeyText(n ast.Node) string { + if n == nil { + return "" + } + switch nn := n.(type) { + case *ast.MappingKeyNode: + return p.mapKeyText(nn.Value) + case *ast.TagNode: + return p.mapKeyText(nn.Value) + case *ast.AnchorNode: + return p.mapKeyText(nn.Value) + case *ast.AliasNode: + return "" + } + return n.GetToken().Value +} + +func (p *parser) parseMapValue(ctx *context, key ast.MapKeyNode, colonTk *Token) (ast.Node, error) { + tk := ctx.currentToken() + if tk == nil { + return newNullNode(ctx, ctx.addNullValueToken(colonTk)) + } + + if ctx.isComment() { + tk = ctx.nextNotCommentToken() + } + keyCol := key.GetToken().Position.Column + keyLine := key.GetToken().Position.Line + + if tk.Column() != keyCol && tk.Line() == keyLine && (tk.GroupType() == TokenGroupMapKey || tk.GroupType() == TokenGroupMapKeyValue) { + // a: b: + // ^ + // + // a: b: c + // ^ + return nil, errors.ErrSyntax("mapping value is not allowed in this context", tk.RawToken()) + } + + if tk.Column() == keyCol && p.isMapToken(tk) { + // in this case, + // ---- + // key: + // next + return newNullNode(ctx, ctx.insertNullToken(colonTk)) + } + + if tk.Line() == keyLine && tk.GroupType() == TokenGroupAnchorName && + ctx.nextToken().Column() == keyCol && p.isMapToken(ctx.nextToken()) { + // in this case, + // ---- + // key: &anchor + // next + group := &TokenGroup{ + Type: TokenGroupAnchor, + Tokens: []*Token{tk, ctx.createImplicitNullToken(tk)}, + } + anchor, err := p.parseAnchor(ctx.withGroup(group), group) + if err != nil { + return nil, err + } + ctx.goNext() + return anchor, nil + } + + if tk.Column() <= keyCol && tk.GroupType() == TokenGroupAnchorName { + // key: + // &anchor + return nil, errors.ErrSyntax("anchor is not allowed in this context", tk.RawToken()) + } + if tk.Column() <= keyCol && tk.Type() == token.TagType { + // key: + // !!tag + return nil, errors.ErrSyntax("tag is not allowed in this context", tk.RawToken()) + } + + if tk.Column() < keyCol { + // in this case, + // ---- + // key: + // next + return newNullNode(ctx, ctx.insertNullToken(colonTk)) + } + + if tk.Line() == keyLine && tk.GroupType() == TokenGroupAnchorName && + ctx.nextToken().Column() < keyCol { + // in this case, + // ---- + // key: &anchor + // next + group := &TokenGroup{ + Type: TokenGroupAnchor, + Tokens: []*Token{tk, ctx.createImplicitNullToken(tk)}, + } + anchor, err := p.parseAnchor(ctx.withGroup(group), group) + if err != nil { + return nil, err + } + ctx.goNext() + return anchor, nil + } + + value, err := p.parseToken(ctx, ctx.currentToken()) + if err != nil { + return nil, err + } + if err := p.validateAnchorValueInMapOrSeq(value, keyCol); err != nil { + return nil, err + } + return value, nil +} + +func (p *parser) validateAnchorValueInMapOrSeq(value ast.Node, col int) error { + anchor, ok := value.(*ast.AnchorNode) + if !ok { + return nil + } + tag, ok := anchor.Value.(*ast.TagNode) + if !ok { + return nil + } + anchorTk := anchor.GetToken() + tagTk := tag.GetToken() + + if anchorTk.Position.Line == tagTk.Position.Line { + // key: + // &anchor !!tag + // + // - &anchor !!tag + return nil + } + + if tagTk.Position.Column <= col { + // key: &anchor + // !!tag + // + // - &anchor + // !!tag + return errors.ErrSyntax("tag is not allowed in this context", tagTk) + } + return nil +} + +func (p *parser) parseAnchor(ctx *context, g *TokenGroup) (*ast.AnchorNode, error) { + anchorNameGroup := g.First().Group + anchor, err := p.parseAnchorName(ctx.withGroup(anchorNameGroup)) + if err != nil { + return nil, err + } + ctx.goNext() + if ctx.isTokenNotFound() { + return nil, errors.ErrSyntax("could not find anchor value", anchor.GetToken()) + } + + value, err := p.parseToken(ctx, ctx.currentToken()) + if err != nil { + return nil, err + } + if _, ok := value.(*ast.AnchorNode); ok { + return nil, errors.ErrSyntax("anchors cannot be used consecutively", value.GetToken()) + } + anchor.Value = value + return anchor, nil +} + +func (p *parser) parseAnchorName(ctx *context) (*ast.AnchorNode, error) { + anchor, err := newAnchorNode(ctx, ctx.currentToken()) + if err != nil { + return nil, err + } + ctx.goNext() + if ctx.isTokenNotFound() { + return nil, errors.ErrSyntax("could not find anchor value", anchor.GetToken()) + } + + anchorName, err := p.parseScalarValue(ctx, ctx.currentToken()) + if err != nil { + return nil, err + } + if anchorName == nil { + return nil, errors.ErrSyntax("unexpected anchor. anchor name is not scalar value", ctx.currentToken().RawToken()) + } + anchor.Name = anchorName + return anchor, nil +} + +func (p *parser) parseAlias(ctx *context) (*ast.AliasNode, error) { + alias, err := newAliasNode(ctx, ctx.currentToken()) + if err != nil { + return nil, err + } + ctx.goNext() + if ctx.isTokenNotFound() { + return nil, errors.ErrSyntax("could not find alias value", alias.GetToken()) + } + + aliasName, err := p.parseScalarValue(ctx, ctx.currentToken()) + if err != nil { + return nil, err + } + if aliasName == nil { + return nil, errors.ErrSyntax("unexpected alias. alias name is not scalar value", ctx.currentToken().RawToken()) + } + alias.Value = aliasName + return alias, nil +} + +func (p *parser) parseLiteral(ctx *context) (*ast.LiteralNode, error) { + node, err := newLiteralNode(ctx, ctx.currentToken()) + if err != nil { + return nil, err + } + ctx.goNext() // skip literal/folded token + + tk := ctx.currentToken() + if tk == nil { + value, err := newStringNode(ctx, &Token{Token: token.New("", "", node.Start.Position)}) + if err != nil { + return nil, err + } + node.Value = value + return node, nil + } + value, err := p.parseToken(ctx, tk) + if err != nil { + return nil, err + } + str, ok := value.(*ast.StringNode) + if !ok { + return nil, errors.ErrSyntax("unexpected token. required string token", value.GetToken()) + } + node.Value = str + return node, nil +} + +func (p *parser) parseScalarTag(ctx *context) (*ast.TagNode, error) { + tag, err := p.parseTag(ctx) + if err != nil { + return nil, err + } + if tag.Value == nil { + return nil, errors.ErrSyntax("specified not scalar tag", tag.GetToken()) + } + if _, ok := tag.Value.(ast.ScalarNode); !ok { + return nil, errors.ErrSyntax("specified not scalar tag", tag.GetToken()) + } + return tag, nil +} + +func (p *parser) parseTag(ctx *context) (*ast.TagNode, error) { + tagTk := ctx.currentToken() + tagRawTk := tagTk.RawToken() + node, err := newTagNode(ctx, tagTk) + if err != nil { + return nil, err + } + ctx.goNext() + + comment := p.parseHeadComment(ctx) + + var tagValue ast.Node + if p.secondaryTagDirective != nil { + value, err := newStringNode(ctx, ctx.currentToken()) + if err != nil { + return nil, err + } + tagValue = value + node.Directive = p.secondaryTagDirective + } else { + value, err := p.parseTagValue(ctx, tagRawTk, ctx.currentToken()) + if err != nil { + return nil, err + } + tagValue = value + } + if err := setHeadComment(comment, tagValue); err != nil { + return nil, err + } + node.Value = tagValue + return node, nil +} + +func (p *parser) parseTagValue(ctx *context, tagRawTk *token.Token, tk *Token) (ast.Node, error) { + if tk == nil { + return newNullNode(ctx, ctx.createImplicitNullToken(&Token{Token: tagRawTk})) + } + switch token.ReservedTagKeyword(tagRawTk.Value) { + case token.MappingTag, token.SetTag: + if !p.isMapToken(tk) { + return nil, errors.ErrSyntax("could not find map", tk.RawToken()) + } + if tk.Type() == token.MappingStartType { + return p.parseFlowMap(ctx.withFlow(true)) + } + return p.parseMap(ctx) + case token.IntegerTag, token.FloatTag, token.StringTag, token.BinaryTag, token.TimestampTag, token.BooleanTag, token.NullTag: + if tk.GroupType() == TokenGroupLiteral || tk.GroupType() == TokenGroupFolded { + return p.parseLiteral(ctx.withGroup(tk.Group)) + } else if tk.Type() == token.CollectEntryType || tk.Type() == token.MappingValueType { + return newTagDefaultScalarValueNode(ctx, tagRawTk) + } + scalar, err := p.parseScalarValue(ctx, tk) + if err != nil { + return nil, err + } + ctx.goNext() + return scalar, nil + case token.SequenceTag, token.OrderedMapTag: + if tk.Type() == token.SequenceStartType { + return p.parseFlowSequence(ctx.withFlow(true)) + } + return p.parseSequence(ctx) + } + return p.parseToken(ctx, tk) +} + +func (p *parser) parseFlowSequence(ctx *context) (*ast.SequenceNode, error) { + node, err := newSequenceNode(ctx, ctx.currentToken(), true) + if err != nil { + return nil, err + } + ctx.goNext() // skip SequenceStart token + + isFirst := true + for ctx.next() { + tk := ctx.currentToken() + if tk.Type() == token.SequenceEndType { + node.End = tk.RawToken() + break + } + + var entryTk *Token + if tk.Type() == token.CollectEntryType { + if isFirst { + return nil, errors.ErrSyntax("expected sequence element, but found ','", tk.RawToken()) + } + entryTk = tk + ctx.goNext() + } else if !isFirst { + return nil, errors.ErrSyntax("',' or ']' must be specified", tk.RawToken()) + } + + if tk := ctx.currentToken(); tk.Type() == token.SequenceEndType { + // this case is here: "[ elem, ]". + // In this case, ignore the last element and break sequence parsing. + node.End = tk.RawToken() + break + } + + if ctx.isTokenNotFound() { + break + } + + ctx := ctx.withIndex(uint(len(node.Values))) + value, err := p.parseToken(ctx, ctx.currentToken()) + if err != nil { + return nil, err + } + node.Values = append(node.Values, value) + seqEntry := ast.SequenceEntry(entryTk.RawToken(), value, nil) + if err := setLineComment(ctx, seqEntry, entryTk); err != nil { + return nil, err + } + seqEntry.SetPath(ctx.path) + node.Entries = append(node.Entries, seqEntry) + + isFirst = false + } + if node.End == nil { + return nil, errors.ErrSyntax("sequence end token ']' not found", node.Start) + } + ctx.goNext() // skip sequence end token. + return node, nil +} + +func (p *parser) parseSequence(ctx *context) (*ast.SequenceNode, error) { + seqTk := ctx.currentToken() + seqNode, err := newSequenceNode(ctx, seqTk, false) + if err != nil { + return nil, err + } + + tk := seqTk + for tk.Type() == token.SequenceEntryType && tk.Column() == seqTk.Column() { + seqTk := tk + headComment := p.parseHeadComment(ctx) + ctx.goNext() // skip sequence entry token + + ctx := ctx.withIndex(uint(len(seqNode.Values))) + value, err := p.parseSequenceValue(ctx, seqTk) + if err != nil { + return nil, err + } + seqEntry := ast.SequenceEntry(seqTk.RawToken(), value, headComment) + if err := setLineComment(ctx, seqEntry, seqTk); err != nil { + return nil, err + } + seqEntry.SetPath(ctx.path) + seqNode.ValueHeadComments = append(seqNode.ValueHeadComments, headComment) + seqNode.Values = append(seqNode.Values, value) + seqNode.Entries = append(seqNode.Entries, seqEntry) + + if ctx.isComment() { + tk = ctx.nextNotCommentToken() + } else { + tk = ctx.currentToken() + } + } + if ctx.isComment() { + if seqTk.Column() <= ctx.currentToken().Column() { + // If the comment is in the same or deeper column as the last element column in sequence value, + // treat it as a footer comment for the last element. + seqNode.FootComment = p.parseFootComment(ctx, seqTk.Column()) + if len(seqNode.Values) != 0 { + seqNode.FootComment.SetPath(seqNode.Values[len(seqNode.Values)-1].GetPath()) + } + } + } + return seqNode, nil +} + +func (p *parser) parseSequenceValue(ctx *context, seqTk *Token) (ast.Node, error) { + tk := ctx.currentToken() + if tk == nil { + return newNullNode(ctx, ctx.addNullValueToken(seqTk)) + } + + if ctx.isComment() { + tk = ctx.nextNotCommentToken() + } + seqCol := seqTk.Column() + seqLine := seqTk.Line() + + if tk.Column() == seqCol && tk.Type() == token.SequenceEntryType { + // in this case, + // ---- + // - + // - + return newNullNode(ctx, ctx.insertNullToken(seqTk)) + } + + if tk.Line() == seqLine && tk.GroupType() == TokenGroupAnchorName && + ctx.nextToken().Column() == seqCol && ctx.nextToken().Type() == token.SequenceEntryType { + // in this case, + // ---- + // - &anchor + // - + group := &TokenGroup{ + Type: TokenGroupAnchor, + Tokens: []*Token{tk, ctx.createImplicitNullToken(tk)}, + } + anchor, err := p.parseAnchor(ctx.withGroup(group), group) + if err != nil { + return nil, err + } + ctx.goNext() + return anchor, nil + } + + if tk.Column() <= seqCol && tk.GroupType() == TokenGroupAnchorName { + // - + // &anchor + return nil, errors.ErrSyntax("anchor is not allowed in this sequence context", tk.RawToken()) + } + if tk.Column() <= seqCol && tk.Type() == token.TagType { + // - + // !!tag + return nil, errors.ErrSyntax("tag is not allowed in this sequence context", tk.RawToken()) + } + + if tk.Column() < seqCol { + // in this case, + // ---- + // - + // next + return newNullNode(ctx, ctx.insertNullToken(seqTk)) + } + + if tk.Line() == seqLine && tk.GroupType() == TokenGroupAnchorName && + ctx.nextToken().Column() < seqCol { + // in this case, + // ---- + // - &anchor + // next + group := &TokenGroup{ + Type: TokenGroupAnchor, + Tokens: []*Token{tk, ctx.createImplicitNullToken(tk)}, + } + anchor, err := p.parseAnchor(ctx.withGroup(group), group) + if err != nil { + return nil, err + } + ctx.goNext() + return anchor, nil + } + + value, err := p.parseToken(ctx, ctx.currentToken()) + if err != nil { + return nil, err + } + if err := p.validateAnchorValueInMapOrSeq(value, seqCol); err != nil { + return nil, err + } + return value, nil +} + +func (p *parser) parseDirective(ctx *context, g *TokenGroup) (*ast.DirectiveNode, error) { + directiveNameGroup := g.First().Group + directive, err := p.parseDirectiveName(ctx.withGroup(directiveNameGroup)) + if err != nil { + return nil, err + } + + switch directive.Name.String() { + case "YAML": + if len(g.Tokens) != 2 { + return nil, errors.ErrSyntax("unexpected format YAML directive", g.First().RawToken()) + } + valueTk := g.Tokens[1] + valueRawTk := valueTk.RawToken() + value := valueRawTk.Value + ver, exists := yamlVersionMap[value] + if !exists { + return nil, errors.ErrSyntax(fmt.Sprintf("unknown YAML version %q", value), valueRawTk) + } + if p.yamlVersion != "" { + return nil, errors.ErrSyntax("YAML version has already been specified", valueRawTk) + } + p.yamlVersion = ver + versionNode, err := newStringNode(ctx, valueTk) + if err != nil { + return nil, err + } + directive.Values = append(directive.Values, versionNode) + case "TAG": + if len(g.Tokens) != 3 { + return nil, errors.ErrSyntax("unexpected format TAG directive", g.First().RawToken()) + } + tagKey, err := newStringNode(ctx, g.Tokens[1]) + if err != nil { + return nil, err + } + if tagKey.Value == "!!" { + p.secondaryTagDirective = directive + } + tagValue, err := newStringNode(ctx, g.Tokens[2]) + if err != nil { + return nil, err + } + directive.Values = append(directive.Values, tagKey, tagValue) + default: + if len(g.Tokens) > 1 { + for _, tk := range g.Tokens[1:] { + value, err := newStringNode(ctx, tk) + if err != nil { + return nil, err + } + directive.Values = append(directive.Values, value) + } + } + } + return directive, nil +} + +func (p *parser) parseDirectiveName(ctx *context) (*ast.DirectiveNode, error) { + directive, err := newDirectiveNode(ctx, ctx.currentToken()) + if err != nil { + return nil, err + } + ctx.goNext() + if ctx.isTokenNotFound() { + return nil, errors.ErrSyntax("could not find directive value", directive.GetToken()) + } + + directiveName, err := p.parseScalarValue(ctx, ctx.currentToken()) + if err != nil { + return nil, err + } + if directiveName == nil { + return nil, errors.ErrSyntax("unexpected directive. directive name is not scalar value", ctx.currentToken().RawToken()) + } + directive.Name = directiveName + return directive, nil +} + +func (p *parser) parseComment(ctx *context) (ast.Node, error) { + cm := p.parseHeadComment(ctx) + if ctx.isTokenNotFound() { + return cm, nil + } + node, err := p.parseToken(ctx, ctx.currentToken()) + if err != nil { + return nil, err + } + if err := setHeadComment(cm, node); err != nil { + return nil, err + } + return node, nil +} + +func (p *parser) parseHeadComment(ctx *context) *ast.CommentGroupNode { + tks := []*token.Token{} + for ctx.isComment() { + tks = append(tks, ctx.currentToken().RawToken()) + ctx.goNext() + } + if len(tks) == 0 { + return nil + } + return ast.CommentGroup(tks) +} + +func (p *parser) parseFootComment(ctx *context, col int) *ast.CommentGroupNode { + tks := []*token.Token{} + for ctx.isComment() && col <= ctx.currentToken().Column() { + tks = append(tks, ctx.currentToken().RawToken()) + ctx.goNext() + } + if len(tks) == 0 { + return nil + } + return ast.CommentGroup(tks) +} diff --git a/vendor/github.com/goccy/go-yaml/parser/token.go b/vendor/github.com/goccy/go-yaml/parser/token.go new file mode 100644 index 000000000..b07c018c4 --- /dev/null +++ b/vendor/github.com/goccy/go-yaml/parser/token.go @@ -0,0 +1,746 @@ +package parser + +import ( + "fmt" + "os" + "strings" + + "github.com/goccy/go-yaml/internal/errors" + "github.com/goccy/go-yaml/token" +) + +type TokenGroupType int + +const ( + TokenGroupNone TokenGroupType = iota + TokenGroupDirective + TokenGroupDirectiveName + TokenGroupDocument + TokenGroupDocumentBody + TokenGroupAnchor + TokenGroupAnchorName + TokenGroupAlias + TokenGroupLiteral + TokenGroupFolded + TokenGroupScalarTag + TokenGroupMapKey + TokenGroupMapKeyValue +) + +func (t TokenGroupType) String() string { + switch t { + case TokenGroupNone: + return "none" + case TokenGroupDirective: + return "directive" + case TokenGroupDirectiveName: + return "directive_name" + case TokenGroupDocument: + return "document" + case TokenGroupDocumentBody: + return "document_body" + case TokenGroupAnchor: + return "anchor" + case TokenGroupAnchorName: + return "anchor_name" + case TokenGroupAlias: + return "alias" + case TokenGroupLiteral: + return "literal" + case TokenGroupFolded: + return "folded" + case TokenGroupScalarTag: + return "scalar_tag" + case TokenGroupMapKey: + return "map_key" + case TokenGroupMapKeyValue: + return "map_key_value" + } + return "none" +} + +type Token struct { + Token *token.Token + Group *TokenGroup + LineComment *token.Token +} + +func (t *Token) RawToken() *token.Token { + if t == nil { + return nil + } + if t.Token != nil { + return t.Token + } + return t.Group.RawToken() +} + +func (t *Token) Type() token.Type { + if t == nil { + return 0 + } + if t.Token != nil { + return t.Token.Type + } + return t.Group.TokenType() +} + +func (t *Token) GroupType() TokenGroupType { + if t == nil { + return TokenGroupNone + } + if t.Token != nil { + return TokenGroupNone + } + return t.Group.Type +} + +func (t *Token) Line() int { + if t == nil { + return 0 + } + if t.Token != nil { + return t.Token.Position.Line + } + return t.Group.Line() +} + +func (t *Token) Column() int { + if t == nil { + return 0 + } + if t.Token != nil { + return t.Token.Position.Column + } + return t.Group.Column() +} + +func (t *Token) SetGroupType(typ TokenGroupType) { + if t.Group == nil { + return + } + t.Group.Type = typ +} + +func (t *Token) Dump() { + ctx := new(groupTokenRenderContext) + if t.Token != nil { + fmt.Fprint(os.Stdout, t.Token.Value) + return + } + t.Group.dump(ctx) + fmt.Fprintf(os.Stdout, "\n") +} + +func (t *Token) dump(ctx *groupTokenRenderContext) { + if t.Token != nil { + fmt.Fprint(os.Stdout, t.Token.Value) + return + } + t.Group.dump(ctx) +} + +type groupTokenRenderContext struct { + num int +} + +type TokenGroup struct { + Type TokenGroupType + Tokens []*Token +} + +func (g *TokenGroup) First() *Token { + if len(g.Tokens) == 0 { + return nil + } + return g.Tokens[0] +} + +func (g *TokenGroup) Last() *Token { + if len(g.Tokens) == 0 { + return nil + } + return g.Tokens[len(g.Tokens)-1] +} + +func (g *TokenGroup) dump(ctx *groupTokenRenderContext) { + num := ctx.num + fmt.Fprint(os.Stdout, colorize(num, "(")) + ctx.num++ + for _, tk := range g.Tokens { + tk.dump(ctx) + } + fmt.Fprint(os.Stdout, colorize(num, ")")) +} + +func (g *TokenGroup) RawToken() *token.Token { + if len(g.Tokens) == 0 { + return nil + } + return g.Tokens[0].RawToken() +} + +func (g *TokenGroup) Line() int { + if len(g.Tokens) == 0 { + return 0 + } + return g.Tokens[0].Line() +} + +func (g *TokenGroup) Column() int { + if len(g.Tokens) == 0 { + return 0 + } + return g.Tokens[0].Column() +} + +func (g *TokenGroup) TokenType() token.Type { + if len(g.Tokens) == 0 { + return 0 + } + return g.Tokens[0].Type() +} + +func CreateGroupedTokens(tokens token.Tokens) ([]*Token, error) { + var err error + tks := newTokens(tokens) + tks = createLineCommentTokenGroups(tks) + tks, err = createLiteralAndFoldedTokenGroups(tks) + if err != nil { + return nil, err + } + tks, err = createAnchorAndAliasTokenGroups(tks) + if err != nil { + return nil, err + } + tks, err = createScalarTagTokenGroups(tks) + if err != nil { + return nil, err + } + tks, err = createAnchorWithScalarTagTokenGroups(tks) + if err != nil { + return nil, err + } + tks, err = createMapKeyTokenGroups(tks) + if err != nil { + return nil, err + } + tks = createMapKeyValueTokenGroups(tks) + tks, err = createDirectiveTokenGroups(tks) + if err != nil { + return nil, err + } + tks, err = createDocumentTokens(tks) + if err != nil { + return nil, err + } + return tks, nil +} + +func newTokens(tks token.Tokens) []*Token { + ret := make([]*Token, 0, len(tks)) + for _, tk := range tks { + ret = append(ret, &Token{Token: tk}) + } + return ret +} + +func createLineCommentTokenGroups(tokens []*Token) []*Token { + ret := make([]*Token, 0, len(tokens)) + for i := 0; i < len(tokens); i++ { + tk := tokens[i] + switch tk.Type() { + case token.CommentType: + if i > 0 && tokens[i-1].Line() == tk.Line() { + tokens[i-1].LineComment = tk.RawToken() + } else { + ret = append(ret, tk) + } + default: + ret = append(ret, tk) + } + } + return ret +} + +func createLiteralAndFoldedTokenGroups(tokens []*Token) ([]*Token, error) { + ret := make([]*Token, 0, len(tokens)) + for i := 0; i < len(tokens); i++ { + tk := tokens[i] + switch tk.Type() { + case token.LiteralType: + tks := []*Token{tk} + if i+1 < len(tokens) { + tks = append(tks, tokens[i+1]) + } + ret = append(ret, &Token{ + Group: &TokenGroup{ + Type: TokenGroupLiteral, + Tokens: tks, + }, + }) + i++ + case token.FoldedType: + tks := []*Token{tk} + if i+1 < len(tokens) { + tks = append(tks, tokens[i+1]) + } + ret = append(ret, &Token{ + Group: &TokenGroup{ + Type: TokenGroupFolded, + Tokens: tks, + }, + }) + i++ + default: + ret = append(ret, tk) + } + } + return ret, nil +} + +func createAnchorAndAliasTokenGroups(tokens []*Token) ([]*Token, error) { + ret := make([]*Token, 0, len(tokens)) + for i := 0; i < len(tokens); i++ { + tk := tokens[i] + switch tk.Type() { + case token.AnchorType: + if i+1 >= len(tokens) { + return nil, errors.ErrSyntax("undefined anchor name", tk.RawToken()) + } + if i+2 >= len(tokens) { + return nil, errors.ErrSyntax("undefined anchor value", tk.RawToken()) + } + anchorName := &Token{ + Group: &TokenGroup{ + Type: TokenGroupAnchorName, + Tokens: []*Token{tk, tokens[i+1]}, + }, + } + valueTk := tokens[i+2] + if tk.Line() == valueTk.Line() && valueTk.Type() == token.SequenceEntryType { + return nil, errors.ErrSyntax("sequence entries are not allowed after anchor on the same line", valueTk.RawToken()) + } + if tk.Line() == valueTk.Line() && isScalarType(valueTk) { + ret = append(ret, &Token{ + Group: &TokenGroup{ + Type: TokenGroupAnchor, + Tokens: []*Token{anchorName, valueTk}, + }, + }) + i++ + } else { + ret = append(ret, anchorName) + } + i++ + case token.AliasType: + if i+1 == len(tokens) { + return nil, errors.ErrSyntax("undefined alias name", tk.RawToken()) + } + ret = append(ret, &Token{ + Group: &TokenGroup{ + Type: TokenGroupAlias, + Tokens: []*Token{tk, tokens[i+1]}, + }, + }) + i++ + default: + ret = append(ret, tk) + } + } + return ret, nil +} + +func createScalarTagTokenGroups(tokens []*Token) ([]*Token, error) { + ret := make([]*Token, 0, len(tokens)) + for i := 0; i < len(tokens); i++ { + tk := tokens[i] + if tk.Type() != token.TagType { + ret = append(ret, tk) + continue + } + tag := tk.RawToken() + if strings.HasPrefix(tag.Value, "!!") { + // secondary tag. + switch token.ReservedTagKeyword(tag.Value) { + case token.IntegerTag, token.FloatTag, token.StringTag, token.BinaryTag, token.TimestampTag, token.BooleanTag, token.NullTag: + if len(tokens) <= i+1 { + ret = append(ret, tk) + continue + } + if tk.Line() != tokens[i+1].Line() { + ret = append(ret, tk) + continue + } + if tokens[i+1].GroupType() == TokenGroupAnchorName { + ret = append(ret, tk) + continue + } + if isScalarType(tokens[i+1]) { + ret = append(ret, &Token{ + Group: &TokenGroup{ + Type: TokenGroupScalarTag, + Tokens: []*Token{tk, tokens[i+1]}, + }, + }) + i++ + } else { + ret = append(ret, tk) + } + case token.MergeTag: + if len(tokens) <= i+1 { + ret = append(ret, tk) + continue + } + if tk.Line() != tokens[i+1].Line() { + ret = append(ret, tk) + continue + } + if tokens[i+1].GroupType() == TokenGroupAnchorName { + ret = append(ret, tk) + continue + } + if tokens[i+1].Type() != token.MergeKeyType { + return nil, errors.ErrSyntax("could not find merge key", tokens[i+1].RawToken()) + } + ret = append(ret, &Token{ + Group: &TokenGroup{ + Type: TokenGroupScalarTag, + Tokens: []*Token{tk, tokens[i+1]}, + }, + }) + i++ + default: + ret = append(ret, tk) + } + } else { + if len(tokens) <= i+1 { + ret = append(ret, tk) + continue + } + if tk.Line() != tokens[i+1].Line() { + ret = append(ret, tk) + continue + } + if tokens[i+1].GroupType() == TokenGroupAnchorName { + ret = append(ret, tk) + continue + } + if isFlowType(tokens[i+1]) { + ret = append(ret, tk) + continue + } + ret = append(ret, &Token{ + Group: &TokenGroup{ + Type: TokenGroupScalarTag, + Tokens: []*Token{tk, tokens[i+1]}, + }, + }) + i++ + } + } + return ret, nil +} + +func createAnchorWithScalarTagTokenGroups(tokens []*Token) ([]*Token, error) { + ret := make([]*Token, 0, len(tokens)) + for i := 0; i < len(tokens); i++ { + tk := tokens[i] + switch tk.GroupType() { + case TokenGroupAnchorName: + if i+1 >= len(tokens) { + return nil, errors.ErrSyntax("undefined anchor value", tk.RawToken()) + } + valueTk := tokens[i+1] + if tk.Line() == valueTk.Line() && valueTk.GroupType() == TokenGroupScalarTag { + ret = append(ret, &Token{ + Group: &TokenGroup{ + Type: TokenGroupAnchor, + Tokens: []*Token{tk, tokens[i+1]}, + }, + }) + i++ + } else { + ret = append(ret, tk) + } + default: + ret = append(ret, tk) + } + } + return ret, nil +} + +func createMapKeyTokenGroups(tokens []*Token) ([]*Token, error) { + tks, err := createMapKeyByMappingKey(tokens) + if err != nil { + return nil, err + } + return createMapKeyByMappingValue(tks) +} + +func createMapKeyByMappingKey(tokens []*Token) ([]*Token, error) { + ret := make([]*Token, 0, len(tokens)) + for i := 0; i < len(tokens); i++ { + tk := tokens[i] + switch tk.Type() { + case token.MappingKeyType: + if i+1 >= len(tokens) { + return nil, errors.ErrSyntax("undefined map key", tk.RawToken()) + } + ret = append(ret, &Token{ + Group: &TokenGroup{ + Type: TokenGroupMapKey, + Tokens: []*Token{tk, tokens[i+1]}, + }, + }) + i++ + default: + ret = append(ret, tk) + } + } + return ret, nil +} + +func createMapKeyByMappingValue(tokens []*Token) ([]*Token, error) { + ret := make([]*Token, 0, len(tokens)) + for i := 0; i < len(tokens); i++ { + tk := tokens[i] + switch tk.Type() { + case token.MappingValueType: + if i == 0 { + return nil, errors.ErrSyntax("unexpected key name", tk.RawToken()) + } + mapKeyTk := tokens[i-1] + if isNotMapKeyType(mapKeyTk) { + return nil, errors.ErrSyntax("found an invalid key for this map", tokens[i].RawToken()) + } + newTk := &Token{Token: mapKeyTk.Token, Group: mapKeyTk.Group} + mapKeyTk.Token = nil + mapKeyTk.Group = &TokenGroup{ + Type: TokenGroupMapKey, + Tokens: []*Token{newTk, tk}, + } + default: + ret = append(ret, tk) + } + } + return ret, nil +} + +func createMapKeyValueTokenGroups(tokens []*Token) []*Token { + ret := make([]*Token, 0, len(tokens)) + for i := 0; i < len(tokens); i++ { + tk := tokens[i] + switch tk.GroupType() { + case TokenGroupMapKey: + if len(tokens) <= i+1 { + ret = append(ret, tk) + continue + } + valueTk := tokens[i+1] + if tk.Line() != valueTk.Line() { + ret = append(ret, tk) + continue + } + if valueTk.GroupType() == TokenGroupAnchorName { + ret = append(ret, tk) + continue + } + if valueTk.Type() == token.TagType && valueTk.GroupType() != TokenGroupScalarTag { + ret = append(ret, tk) + continue + } + + if isScalarType(valueTk) || valueTk.Type() == token.TagType { + ret = append(ret, &Token{ + Group: &TokenGroup{ + Type: TokenGroupMapKeyValue, + Tokens: []*Token{tk, valueTk}, + }, + }) + i++ + } else { + ret = append(ret, tk) + continue + } + default: + ret = append(ret, tk) + } + } + return ret +} + +func createDirectiveTokenGroups(tokens []*Token) ([]*Token, error) { + ret := make([]*Token, 0, len(tokens)) + for i := 0; i < len(tokens); i++ { + tk := tokens[i] + switch tk.Type() { + case token.DirectiveType: + if i+1 >= len(tokens) { + return nil, errors.ErrSyntax("undefined directive value", tk.RawToken()) + } + directiveName := &Token{ + Group: &TokenGroup{ + Type: TokenGroupDirectiveName, + Tokens: []*Token{tk, tokens[i+1]}, + }, + } + i++ + var valueTks []*Token + for j := i + 1; j < len(tokens); j++ { + if tokens[j].Line() != tk.Line() { + break + } + valueTks = append(valueTks, tokens[j]) + i++ + } + if i+1 >= len(tokens) || tokens[i+1].Type() != token.DocumentHeaderType { + return nil, errors.ErrSyntax("unexpected directive value. document not started", tk.RawToken()) + } + if len(valueTks) != 0 { + ret = append(ret, &Token{ + Group: &TokenGroup{ + Type: TokenGroupDirective, + Tokens: append([]*Token{directiveName}, valueTks...), + }, + }) + } else { + ret = append(ret, directiveName) + } + default: + ret = append(ret, tk) + } + } + return ret, nil +} + +func createDocumentTokens(tokens []*Token) ([]*Token, error) { + var ret []*Token + for i := 0; i < len(tokens); i++ { + tk := tokens[i] + switch tk.Type() { + case token.DocumentHeaderType: + if i != 0 { + ret = append(ret, &Token{ + Group: &TokenGroup{Tokens: tokens[:i]}, + }) + } + if i+1 == len(tokens) { + // if current token is last token, add DocumentHeader only tokens to ret. + return append(ret, &Token{ + Group: &TokenGroup{ + Type: TokenGroupDocument, + Tokens: []*Token{tk}, + }, + }), nil + } + if tokens[i+1].Type() == token.DocumentHeaderType { + return append(ret, &Token{ + Group: &TokenGroup{ + Type: TokenGroupDocument, + Tokens: []*Token{tk}, + }, + }), nil + } + if tokens[i].Line() == tokens[i+1].Line() { + switch tokens[i+1].GroupType() { + case TokenGroupMapKey, TokenGroupMapKeyValue: + return nil, errors.ErrSyntax("value cannot be placed after document separator", tokens[i+1].RawToken()) + } + switch tokens[i+1].Type() { + case token.SequenceEntryType: + return nil, errors.ErrSyntax("value cannot be placed after document separator", tokens[i+1].RawToken()) + } + } + tks, err := createDocumentTokens(tokens[i+1:]) + if err != nil { + return nil, err + } + if len(tks) != 0 { + tks[0].SetGroupType(TokenGroupDocument) + tks[0].Group.Tokens = append([]*Token{tk}, tks[0].Group.Tokens...) + return append(ret, tks...), nil + } + return append(ret, &Token{ + Group: &TokenGroup{ + Type: TokenGroupDocument, + Tokens: []*Token{tk}, + }, + }), nil + case token.DocumentEndType: + if i != 0 { + ret = append(ret, &Token{ + Group: &TokenGroup{ + Type: TokenGroupDocument, + Tokens: tokens[0 : i+1], + }, + }) + } + if i+1 == len(tokens) { + return ret, nil + } + if isScalarType(tokens[i+1]) { + return nil, errors.ErrSyntax("unexpected end content", tokens[i+1].RawToken()) + } + + tks, err := createDocumentTokens(tokens[i+1:]) + if err != nil { + return nil, err + } + return append(ret, tks...), nil + } + } + return append(ret, &Token{ + Group: &TokenGroup{ + Type: TokenGroupDocument, + Tokens: tokens, + }, + }), nil +} + +func isScalarType(tk *Token) bool { + switch tk.GroupType() { + case TokenGroupMapKey, TokenGroupMapKeyValue: + return false + } + typ := tk.Type() + return typ == token.AnchorType || + typ == token.AliasType || + typ == token.LiteralType || + typ == token.FoldedType || + typ == token.NullType || + typ == token.ImplicitNullType || + typ == token.BoolType || + typ == token.IntegerType || + typ == token.BinaryIntegerType || + typ == token.OctetIntegerType || + typ == token.HexIntegerType || + typ == token.FloatType || + typ == token.InfinityType || + typ == token.NanType || + typ == token.StringType || + typ == token.SingleQuoteType || + typ == token.DoubleQuoteType +} + +func isNotMapKeyType(tk *Token) bool { + typ := tk.Type() + return typ == token.DirectiveType || + typ == token.DocumentHeaderType || + typ == token.DocumentEndType || + typ == token.CollectEntryType || + typ == token.MappingStartType || + typ == token.MappingValueType || + typ == token.MappingEndType || + typ == token.SequenceStartType || + typ == token.SequenceEntryType || + typ == token.SequenceEndType +} + +func isFlowType(tk *Token) bool { + typ := tk.Type() + return typ == token.MappingStartType || + typ == token.MappingEndType || + typ == token.SequenceStartType || + typ == token.SequenceEntryType +} diff --git a/vendor/github.com/goccy/go-yaml/path.go b/vendor/github.com/goccy/go-yaml/path.go index b79c6669f..4d2cbbaee 100644 --- a/vendor/github.com/goccy/go-yaml/path.go +++ b/vendor/github.com/goccy/go-yaml/path.go @@ -8,7 +8,6 @@ import ( "strings" "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" ) @@ -39,7 +38,7 @@ func PathString(s string) (*Path, error) { case '.': b, buf, c, err := parsePathDot(builder, buf, cursor) if err != nil { - return nil, errors.Wrapf(err, "failed to parse path of dot") + return nil, err } length = len(buf) builder = b @@ -47,13 +46,13 @@ func PathString(s string) (*Path, error) { case '[': b, buf, c, err := parsePathIndex(builder, buf, cursor) if err != nil { - return nil, errors.Wrapf(err, "failed to parse path of index") + return nil, err } length = len(buf) builder = b cursor = c default: - return nil, errors.Wrapf(ErrInvalidPathString, "invalid path at %d", cursor) + return nil, fmt.Errorf("invalid path at %d: %w", cursor, ErrInvalidPathString) } } return builder.Build(), nil @@ -67,28 +66,31 @@ func parsePathRecursive(b *PathBuilder, buf []rune, cursor int) (*PathBuilder, [ c := buf[cursor] switch c { case '$': - return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "specified '$' after '..' character") + return nil, nil, 0, fmt.Errorf("specified '$' after '..' character: %w", ErrInvalidPathString) case '*': - return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "specified '*' after '..' character") + return nil, nil, 0, fmt.Errorf("specified '*' after '..' character: %w", ErrInvalidPathString) case '.', '[': goto end case ']': - return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "specified ']' after '..' character") + return nil, nil, 0, fmt.Errorf("specified ']' after '..' character: %w", ErrInvalidPathString) } } end: if start == cursor { - return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "not found recursive selector") + return nil, nil, 0, fmt.Errorf("not found recursive selector: %w", ErrInvalidPathString) } return b.Recursive(string(buf[start:cursor])), buf, cursor, nil } func parsePathDot(b *PathBuilder, buf []rune, cursor int) (*PathBuilder, []rune, int, error) { + if b.root == nil || b.node == nil { + return nil, nil, 0, fmt.Errorf("required '$' character at first: %w", ErrInvalidPathString) + } length := len(buf) if cursor+1 < length && buf[cursor+1] == '.' { b, buf, c, err := parsePathRecursive(b, buf, cursor) if err != nil { - return nil, nil, 0, errors.Wrapf(err, "failed to parse path of recursive") + return nil, nil, 0, err } return b, buf, c, nil } @@ -103,23 +105,27 @@ func parsePathDot(b *PathBuilder, buf []rune, cursor int) (*PathBuilder, []rune, c := buf[cursor] switch c { case '$': - return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "specified '$' after '.' character") + return nil, nil, 0, fmt.Errorf("specified '$' after '.' character: %w", ErrInvalidPathString) case '*': - return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "specified '*' after '.' character") + return nil, nil, 0, fmt.Errorf("specified '*' after '.' character: %w", ErrInvalidPathString) case '.', '[': goto end case ']': - return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "specified ']' after '.' character") + return nil, nil, 0, fmt.Errorf("specified ']' after '.' character: %w", ErrInvalidPathString) } } end: if start == cursor { - return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "cloud not find by empty key") + return nil, nil, 0, fmt.Errorf("could not find by empty key: %w", ErrInvalidPathString) } return b.child(string(buf[start:cursor])), buf, cursor, nil } func parseQuotedKey(b *PathBuilder, buf []rune, cursor int) (*PathBuilder, []rune, int, error) { + if b.root == nil || b.node == nil { + return nil, nil, 0, fmt.Errorf("required '$' character at first: %w", ErrInvalidPathString) + } + cursor++ // skip single quote start := cursor length := len(buf) @@ -136,31 +142,35 @@ func parseQuotedKey(b *PathBuilder, buf []rune, cursor int) (*PathBuilder, []run } end: if !foundEndDelim { - return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "could not find end delimiter for key") + return nil, nil, 0, fmt.Errorf("could not find end delimiter for key: %w", ErrInvalidPathString) } if start == cursor { - return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "could not find by empty key") + return nil, nil, 0, fmt.Errorf("could not find by empty key: %w", ErrInvalidPathString) } selector := buf[start:cursor] cursor++ if cursor < length { switch buf[cursor] { case '$': - return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "specified '$' after '.' character") + return nil, nil, 0, fmt.Errorf("specified '$' after '.' character: %w", ErrInvalidPathString) case '*': - return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "specified '*' after '.' character") + return nil, nil, 0, fmt.Errorf("specified '*' after '.' character: %w", ErrInvalidPathString) case ']': - return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "specified ']' after '.' character") + return nil, nil, 0, fmt.Errorf("specified ']' after '.' character: %w", ErrInvalidPathString) } } return b.child(string(selector)), buf, cursor, nil } func parsePathIndex(b *PathBuilder, buf []rune, cursor int) (*PathBuilder, []rune, int, error) { + if b.root == nil || b.node == nil { + return nil, nil, 0, fmt.Errorf("required '$' character at first: %w", ErrInvalidPathString) + } + length := len(buf) cursor++ // skip '[' character if length <= cursor { - return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "unexpected end of YAML Path") + return nil, nil, 0, fmt.Errorf("unexpected end of YAML Path: %w", ErrInvalidPathString) } c := buf[cursor] switch c { @@ -176,7 +186,7 @@ func parsePathIndex(b *PathBuilder, buf []rune, cursor int) (*PathBuilder, []run break } if buf[cursor] != ']' { - return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "invalid character %s at %d", string(buf[cursor]), cursor) + return nil, nil, 0, fmt.Errorf("invalid character %s at %d: %w", string(buf[cursor]), cursor, ErrInvalidPathString) } numOrAll := string(buf[start:cursor]) if numOrAll == "*" { @@ -184,11 +194,11 @@ func parsePathIndex(b *PathBuilder, buf []rune, cursor int) (*PathBuilder, []run } num, err := strconv.ParseInt(numOrAll, 10, 64) if err != nil { - return nil, nil, 0, errors.Wrapf(err, "failed to parse number") + return nil, nil, 0, err } return b.Index(uint(num)), buf, cursor + 1, nil } - return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "invalid character %s at %d", c, cursor) + return nil, nil, 0, fmt.Errorf("invalid character %q at %d: %w", c, cursor, ErrInvalidPathString) } // Path represent YAMLPath ( like a JSONPath ). @@ -205,10 +215,10 @@ func (p *Path) String() string { func (p *Path) Read(r io.Reader, v interface{}) error { node, err := p.ReadNode(r) if err != nil { - return errors.Wrapf(err, "failed to read node") + return err } if err := Unmarshal([]byte(node.String()), v); err != nil { - return errors.Wrapf(err, "failed to unmarshal") + return err } return nil } @@ -220,15 +230,15 @@ func (p *Path) ReadNode(r io.Reader) (ast.Node, error) { } var buf bytes.Buffer if _, err := io.Copy(&buf, r); err != nil { - return nil, errors.Wrapf(err, "failed to copy from reader") + return nil, err } f, err := parser.ParseBytes(buf.Bytes(), 0) if err != nil { - return nil, errors.Wrapf(err, "failed to parse yaml") + return nil, err } node, err := p.FilterFile(f) if err != nil { - return nil, errors.Wrapf(err, "failed to filter from ast.File") + return nil, err } return node, nil } @@ -237,10 +247,10 @@ func (p *Path) ReadNode(r io.Reader) (ast.Node, error) { func (p *Path) Filter(target, v interface{}) error { b, err := Marshal(target) if err != nil { - return errors.Wrapf(err, "failed to marshal target value") + return err } if err := p.Read(bytes.NewBuffer(b), v); err != nil { - return errors.Wrapf(err, "failed to read") + return err } return nil } @@ -250,20 +260,23 @@ func (p *Path) FilterFile(f *ast.File) (ast.Node, error) { for _, doc := range f.Docs { node, err := p.FilterNode(doc.Body) if err != nil { - return nil, errors.Wrapf(err, "failed to filter node by path ( %s )", p.node) + return nil, err } if node != nil { return node, nil } } - return nil, errors.Wrapf(ErrNotFoundNode, "failed to find path ( %s )", p.node) + return nil, fmt.Errorf("failed to find path ( %s ): %w", p.node, ErrNotFoundNode) } // FilterNode filter from node by YAMLPath. func (p *Path) FilterNode(node ast.Node) (ast.Node, error) { + if node == nil { + return nil, nil + } n, err := p.node.filter(node) if err != nil { - return nil, errors.Wrapf(err, "failed to filter node by path ( %s )", p.node) + return nil, err } return n, nil } @@ -272,14 +285,14 @@ func (p *Path) FilterNode(node ast.Node) (ast.Node, error) { func (p *Path) MergeFromReader(dst *ast.File, src io.Reader) error { var buf bytes.Buffer if _, err := io.Copy(&buf, src); err != nil { - return errors.Wrapf(err, "failed to copy from reader") + return err } file, err := parser.ParseBytes(buf.Bytes(), 0) if err != nil { - return errors.Wrapf(err, "failed to parse") + return err } if err := p.MergeFromFile(dst, file); err != nil { - return errors.Wrapf(err, "failed to merge file") + return err } return nil } @@ -288,11 +301,11 @@ func (p *Path) MergeFromReader(dst *ast.File, src io.Reader) error { func (p *Path) MergeFromFile(dst *ast.File, src *ast.File) error { base, err := p.FilterFile(dst) if err != nil { - return errors.Wrapf(err, "failed to filter file") + return err } for _, doc := range src.Docs { if err := ast.Merge(base, doc); err != nil { - return errors.Wrapf(err, "failed to merge") + return err } } return nil @@ -302,10 +315,10 @@ func (p *Path) MergeFromFile(dst *ast.File, src *ast.File) error { func (p *Path) MergeFromNode(dst *ast.File, src ast.Node) error { base, err := p.FilterFile(dst) if err != nil { - return errors.Wrapf(err, "failed to filter file") + return err } if err := ast.Merge(base, src); err != nil { - return errors.Wrapf(err, "failed to merge") + return err } return nil } @@ -314,14 +327,14 @@ func (p *Path) MergeFromNode(dst *ast.File, src ast.Node) error { func (p *Path) ReplaceWithReader(dst *ast.File, src io.Reader) error { var buf bytes.Buffer if _, err := io.Copy(&buf, src); err != nil { - return errors.Wrapf(err, "failed to copy from reader") + return err } file, err := parser.ParseBytes(buf.Bytes(), 0) if err != nil { - return errors.Wrapf(err, "failed to parse") + return err } if err := p.ReplaceWithFile(dst, file); err != nil { - return errors.Wrapf(err, "failed to replace file") + return err } return nil } @@ -330,7 +343,7 @@ func (p *Path) ReplaceWithReader(dst *ast.File, src io.Reader) error { func (p *Path) ReplaceWithFile(dst *ast.File, src *ast.File) error { for _, doc := range src.Docs { if err := p.ReplaceWithNode(dst, doc); err != nil { - return errors.Wrapf(err, "failed to replace file by path ( %s )", p.node) + return err } } return nil @@ -343,7 +356,7 @@ func (p *Path) ReplaceWithNode(dst *ast.File, node ast.Node) error { node = node.(*ast.DocumentNode).Body } if err := p.node.replace(doc.Body, node); err != nil { - return errors.Wrapf(err, "failed to replace node by path ( %s )", p.node) + return err } } return nil @@ -472,7 +485,7 @@ func (n *rootNode) filter(node ast.Node) (ast.Node, error) { } filtered, err := n.child.filter(node) if err != nil { - return nil, errors.Wrapf(err, "failed to filter") + return nil, err } return filtered, nil } @@ -482,7 +495,7 @@ func (n *rootNode) replace(node ast.Node, target ast.Node) error { return nil } if err := n.child.replace(node, target); err != nil { - return errors.Wrapf(err, "failed to replace") + return err } return nil } @@ -514,7 +527,7 @@ func (n *selectorNode) filter(node ast.Node) (ast.Node, error) { var err error key, err = strconv.Unquote(key) if err != nil { - return nil, errors.Wrapf(err, "failed to unquote") + return nil, err } case '\'': if len(key) > 1 && key[len(key)-1] == '\'' { @@ -528,13 +541,13 @@ func (n *selectorNode) filter(node ast.Node) (ast.Node, error) { } filtered, err := n.child.filter(value.Value) if err != nil { - return nil, errors.Wrapf(err, "failed to filter") + return nil, err } return filtered, nil } } case ast.MappingValueType: - value := node.(*ast.MappingValueNode) + value, _ := node.(*ast.MappingValueNode) key := value.Key.GetToken().Value if key == selector { if n.child == nil { @@ -542,12 +555,12 @@ func (n *selectorNode) filter(node ast.Node) (ast.Node, error) { } filtered, err := n.child.filter(value.Value) if err != nil { - return nil, errors.Wrapf(err, "failed to filter") + return nil, err } return filtered, nil } default: - return nil, errors.Wrapf(ErrInvalidQuery, "expected node type is map or map value. but got %s", node.Type()) + return nil, fmt.Errorf("expected node type is map or map value. but got %s: %w", node.Type(), ErrInvalidQuery) } return nil, nil } @@ -559,11 +572,11 @@ func (n *selectorNode) replaceMapValue(value *ast.MappingValueNode, target ast.N } if n.child == nil { if err := value.Replace(target); err != nil { - return errors.Wrapf(err, "failed to replace") + return err } } else { if err := n.child.replace(value.Value, target); err != nil { - return errors.Wrapf(err, "failed to replace") + return err } } return nil @@ -574,16 +587,16 @@ func (n *selectorNode) replace(node ast.Node, target ast.Node) error { case ast.MappingType: for _, value := range node.(*ast.MappingNode).Values { if err := n.replaceMapValue(value, target); err != nil { - return errors.Wrapf(err, "failed to replace map value") + return err } } 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") + return err } default: - return errors.Wrapf(ErrInvalidQuery, "expected node type is map or map value. but got %s", node.Type()) + return fmt.Errorf("expected node type is map or map value. but got %s: %w", node.Type(), ErrInvalidQuery) } return nil } @@ -612,11 +625,11 @@ func newIndexNode(selector uint) *indexNode { 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()) + return nil, fmt.Errorf("expected sequence type node. but got %s: %w", node.Type(), ErrInvalidQuery) } - 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) + return nil, fmt.Errorf("expected index is %d. but got sequences has %d items: %w", n.selector, len(sequence.Values), ErrInvalidQuery) } value := sequence.Values[n.selector] if n.child == nil { @@ -624,27 +637,27 @@ func (n *indexNode) filter(node ast.Node) (ast.Node, error) { } filtered, err := n.child.filter(value) if err != nil { - return nil, errors.Wrapf(err, "failed to filter") + return nil, err } return filtered, nil } 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()) + return fmt.Errorf("expected sequence type node. but got %s: %w", node.Type(), ErrInvalidQuery) } - 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) + return fmt.Errorf("expected index is %d. but got sequences has %d items: %w", n.selector, len(sequence.Values), ErrInvalidQuery) } if n.child == nil { if err := sequence.Replace(int(n.selector), target); err != nil { - return errors.Wrapf(err, "failed to replace") + return err } return nil } if err := n.child.replace(sequence.Values[n.selector], target); err != nil { - return errors.Wrapf(err, "failed to replace") + return err } return nil } @@ -677,9 +690,9 @@ func (n *indexAllNode) String() string { 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()) + return nil, fmt.Errorf("expected sequence type node. but got %s: %w", node.Type(), ErrInvalidQuery) } - sequence := node.(*ast.SequenceNode) + sequence, _ := node.(*ast.SequenceNode) if n.child == nil { return sequence, nil } @@ -688,7 +701,7 @@ func (n *indexAllNode) filter(node ast.Node) (ast.Node, error) { for _, value := range sequence.Values { filtered, err := n.child.filter(value) if err != nil { - return nil, errors.Wrapf(err, "failed to filter") + return nil, err } out.Values = append(out.Values, filtered) } @@ -697,20 +710,20 @@ func (n *indexAllNode) filter(node ast.Node) (ast.Node, error) { 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()) + return fmt.Errorf("expected sequence type node. but got %s: %w", node.Type(), ErrInvalidQuery) } - 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 { - return errors.Wrapf(err, "failed to replace") + return err } } return nil } for _, value := range sequence.Values { if err := n.child.replace(value, target); err != nil { - return errors.Wrapf(err, "failed to replace") + return err } } return nil @@ -743,7 +756,7 @@ func (n *recursiveNode) filterNode(node ast.Node) (*ast.SequenceNode, error) { for _, value := range typedNode.Values { seq, err := n.filterNode(value) if err != nil { - return nil, errors.Wrapf(err, "failed to filter") + return nil, err } sequence.Values = append(sequence.Values, seq.Values...) } @@ -754,14 +767,14 @@ func (n *recursiveNode) filterNode(node ast.Node) (*ast.SequenceNode, error) { } seq, err := n.filterNode(typedNode.Value) if err != nil { - return nil, errors.Wrapf(err, "failed to filter") + return nil, err } sequence.Values = append(sequence.Values, seq.Values...) case *ast.SequenceNode: for _, value := range typedNode.Values { seq, err := n.filterNode(value) if err != nil { - return nil, errors.Wrapf(err, "failed to filter") + return nil, err } sequence.Values = append(sequence.Values, seq.Values...) } @@ -772,7 +785,7 @@ func (n *recursiveNode) filterNode(node ast.Node) (*ast.SequenceNode, error) { func (n *recursiveNode) filter(node ast.Node) (ast.Node, error) { sequence, err := n.filterNode(node) if err != nil { - return nil, errors.Wrapf(err, "failed to filter") + return nil, err } sequence.Start = node.GetToken() return sequence, nil @@ -783,23 +796,23 @@ func (n *recursiveNode) replaceNode(node ast.Node, target ast.Node) error { case *ast.MappingNode: for _, value := range typedNode.Values { if err := n.replaceNode(value, target); err != nil { - return errors.Wrapf(err, "failed to replace") + return err } } case *ast.MappingValueNode: key := typedNode.Key.GetToken().Value if n.selector == key { if err := typedNode.Replace(target); err != nil { - return errors.Wrapf(err, "failed to replace") + return err } } if err := n.replaceNode(typedNode.Value, target); err != nil { - return errors.Wrapf(err, "failed to replace") + return err } case *ast.SequenceNode: for _, value := range typedNode.Values { if err := n.replaceNode(value, target); err != nil { - return errors.Wrapf(err, "failed to replace") + return err } } } @@ -808,7 +821,7 @@ func (n *recursiveNode) replaceNode(node ast.Node, target ast.Node) error { func (n *recursiveNode) replace(node ast.Node, target ast.Node) error { if err := n.replaceNode(node, target); err != nil { - return errors.Wrapf(err, "failed to replace") + return err } return nil } diff --git a/vendor/github.com/goccy/go-yaml/printer/color.go b/vendor/github.com/goccy/go-yaml/printer/color.go new file mode 100644 index 000000000..79d7d7c6c --- /dev/null +++ b/vendor/github.com/goccy/go-yaml/printer/color.go @@ -0,0 +1,83 @@ +// This source inspired by https://github.com/fatih/color. +package printer + +import ( + "fmt" + "strings" +) + +type ColorAttribute int + +const ( + ColorReset ColorAttribute = iota + ColorBold + ColorFaint + ColorItalic + ColorUnderline + ColorBlinkSlow + ColorBlinkRapid + ColorReverseVideo + ColorConcealed + ColorCrossedOut +) + +const ( + ColorFgHiBlack ColorAttribute = iota + 90 + ColorFgHiRed + ColorFgHiGreen + ColorFgHiYellow + ColorFgHiBlue + ColorFgHiMagenta + ColorFgHiCyan + ColorFgHiWhite +) + +const ( + ColorResetBold ColorAttribute = iota + 22 + ColorResetItalic + ColorResetUnderline + ColorResetBlinking + + ColorResetReversed + ColorResetConcealed + ColorResetCrossedOut +) + +const escape = "\x1b" + +var colorResetMap = map[ColorAttribute]ColorAttribute{ + ColorBold: ColorResetBold, + ColorFaint: ColorResetBold, + ColorItalic: ColorResetItalic, + ColorUnderline: ColorResetUnderline, + ColorBlinkSlow: ColorResetBlinking, + ColorBlinkRapid: ColorResetBlinking, + ColorReverseVideo: ColorResetReversed, + ColorConcealed: ColorResetConcealed, + ColorCrossedOut: ColorResetCrossedOut, +} + +func format(attrs ...ColorAttribute) string { + format := make([]string, 0, len(attrs)) + for _, attr := range attrs { + format = append(format, fmt.Sprint(attr)) + } + return fmt.Sprintf("%s[%sm", escape, strings.Join(format, ";")) +} + +func unformat(attrs ...ColorAttribute) string { + format := make([]string, len(attrs)) + for _, attr := range attrs { + v := fmt.Sprint(ColorReset) + reset, exists := colorResetMap[attr] + if exists { + v = fmt.Sprint(reset) + } + format = append(format, v) + } + return fmt.Sprintf("%s[%sm", escape, strings.Join(format, ";")) +} + +func colorize(msg string, attrs ...ColorAttribute) string { + return format(attrs...) + msg + unformat(attrs...) +} diff --git a/vendor/github.com/goccy/go-yaml/printer/printer.go b/vendor/github.com/goccy/go-yaml/printer/printer.go index d5e25dc91..9346983da 100644 --- a/vendor/github.com/goccy/go-yaml/printer/printer.go +++ b/vendor/github.com/goccy/go-yaml/printer/printer.go @@ -5,7 +5,6 @@ import ( "math" "strings" - "github.com/fatih/color" "github.com/goccy/go-yaml/ast" "github.com/goccy/go-yaml/token" ) @@ -29,6 +28,7 @@ type Printer struct { Bool PrintFunc String PrintFunc Number PrintFunc + Comment PrintFunc } func defaultLineNumberFormat(num int) string { @@ -82,6 +82,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 @@ -144,47 +149,47 @@ func (p *Printer) PrintNode(node ast.Node) []byte { return []byte(fmt.Sprintf("%+v\n", node)) } -const escape = "\x1b" - -func format(attr color.Attribute) string { - return fmt.Sprintf("%s[%dm", escape, attr) -} - func (p *Printer) setDefaultColorSet() { p.Bool = func() *Property { return &Property{ - Prefix: format(color.FgHiMagenta), - Suffix: format(color.Reset), + Prefix: format(ColorFgHiMagenta), + Suffix: format(ColorReset), } } p.Number = func() *Property { return &Property{ - Prefix: format(color.FgHiMagenta), - Suffix: format(color.Reset), + Prefix: format(ColorFgHiMagenta), + Suffix: format(ColorReset), } } p.MapKey = func() *Property { return &Property{ - Prefix: format(color.FgHiCyan), - Suffix: format(color.Reset), + Prefix: format(ColorFgHiCyan), + Suffix: format(ColorReset), } } p.Anchor = func() *Property { return &Property{ - Prefix: format(color.FgHiYellow), - Suffix: format(color.Reset), + Prefix: format(ColorFgHiYellow), + Suffix: format(ColorReset), } } p.Alias = func() *Property { return &Property{ - Prefix: format(color.FgHiYellow), - Suffix: format(color.Reset), + Prefix: format(ColorFgHiYellow), + Suffix: format(ColorReset), } } p.String = func() *Property { return &Property{ - Prefix: format(color.FgHiGreen), - Suffix: format(color.Reset), + Prefix: format(ColorFgHiGreen), + Suffix: format(ColorReset), + } + } + p.Comment = func() *Property { + return &Property{ + Prefix: format(ColorFgHiBlack), + Suffix: format(ColorReset), } } } @@ -192,9 +197,9 @@ func (p *Printer) setDefaultColorSet() { func (p *Printer) PrintErrorMessage(msg string, isColored bool) string { if isColored { return fmt.Sprintf("%s%s%s", - format(color.FgHiRed), + format(ColorFgHiRed), msg, - format(color.Reset), + format(ColorReset), ) } return msg @@ -246,10 +251,7 @@ func (p *Printer) isNewLineLastChar(s string) bool { } func (p *Printer) printBeforeTokens(tk *token.Token, minLine, extLine int) token.Tokens { - for { - if tk.Prev == nil { - break - } + for tk.Prev != nil { if tk.Prev.Position.Line < minLine { break } @@ -317,8 +319,7 @@ func (p *Printer) setupErrorTokenFormat(annotateLine int, isColored bool) { p.LineNumber = true p.LineNumberFormat = func(num int) string { if isColored { - fn := color.New(color.Bold, color.FgHiWhite).SprintFunc() - return fn(prefix(annotateLine, num)) + return colorize(prefix(annotateLine, num), ColorBold, ColorFgHiWhite) } return prefix(annotateLine, num) } diff --git a/vendor/github.com/goccy/go-yaml/scanner/context.go b/vendor/github.com/goccy/go-yaml/scanner/context.go index 3aaec5618..4f3250be8 100644 --- a/vendor/github.com/goccy/go-yaml/scanner/context.go +++ b/vendor/github.com/goccy/go-yaml/scanner/context.go @@ -1,13 +1,14 @@ package scanner import ( + "errors" + "strconv" + "strings" "sync" "github.com/goccy/go-yaml/token" ) -const whitespace = ' ' - // Context context at scanning type Context struct { idx int @@ -18,11 +19,20 @@ type Context struct { buf []rune obuf []rune tokens token.Tokens - isRawFolded bool - isLiteral bool - isFolded bool - isSingleLine bool - literalOpt string + mstate *MultiLineState +} + +type MultiLineState struct { + opt string + firstLineIndentColumn int + prevLineIndentColumn int + lineIndentColumn int + lastNotSpaceOnlyLineIndentColumn int + spaceOnlyIndentColumn int + foldedNewLine bool + isRawFolded bool + isLiteral bool + isFolded bool } var ( @@ -35,14 +45,13 @@ var ( func createContext() *Context { return &Context{ - idx: 0, - tokens: token.Tokens{}, - isSingleLine: true, + idx: 0, + tokens: token.Tokens{}, } } func newContext(src []rune) *Context { - ctx := ctxPool.Get().(*Context) + ctx, _ := ctxPool.Get().(*Context) ctx.reset(src) return ctx } @@ -51,17 +60,18 @@ func (c *Context) release() { ctxPool.Put(c) } +func (c *Context) clear() { + c.resetBuffer() + c.mstate = nil +} + func (c *Context) reset(src []rune) { c.idx = 0 c.size = len(src) c.src = src c.tokens = c.tokens[:0] c.resetBuffer() - c.isRawFolded = false - c.isSingleLine = true - c.isLiteral = false - c.isFolded = false - c.literalOpt = "" + c.mstate = nil } func (c *Context) resetBuffer() { @@ -71,15 +81,185 @@ func (c *Context) resetBuffer() { c.notSpaceOrgCharPos = 0 } -func (c *Context) isSaveIndentMode() bool { - return c.isLiteral || c.isFolded || c.isRawFolded +func (c *Context) breakMultiLine() { + c.mstate = nil } -func (c *Context) breakLiteral() { - c.isLiteral = false - c.isRawFolded = false - c.isFolded = false - c.literalOpt = "" +func (c *Context) getMultiLineState() *MultiLineState { + return c.mstate +} + +func (c *Context) setLiteral(lastDelimColumn int, opt string) { + mstate := &MultiLineState{ + isLiteral: true, + opt: opt, + } + indent := firstLineIndentColumnByOpt(opt) + if indent > 0 { + mstate.firstLineIndentColumn = lastDelimColumn + indent + } + c.mstate = mstate +} + +func (c *Context) setFolded(lastDelimColumn int, opt string) { + mstate := &MultiLineState{ + isFolded: true, + opt: opt, + } + indent := firstLineIndentColumnByOpt(opt) + if indent > 0 { + mstate.firstLineIndentColumn = lastDelimColumn + indent + } + c.mstate = mstate +} + +func (c *Context) setRawFolded(column int) { + mstate := &MultiLineState{ + isRawFolded: true, + } + mstate.updateIndentColumn(column) + c.mstate = mstate +} + +func firstLineIndentColumnByOpt(opt string) int { + opt = strings.TrimPrefix(opt, "-") + opt = strings.TrimPrefix(opt, "+") + opt = strings.TrimSuffix(opt, "-") + opt = strings.TrimSuffix(opt, "+") + i, _ := strconv.ParseInt(opt, 10, 64) + return int(i) +} + +func (s *MultiLineState) lastDelimColumn() int { + if s.firstLineIndentColumn == 0 { + return 0 + } + return s.firstLineIndentColumn - 1 +} + +func (s *MultiLineState) updateIndentColumn(column int) { + if s.firstLineIndentColumn == 0 { + s.firstLineIndentColumn = column + } + if s.lineIndentColumn == 0 { + s.lineIndentColumn = column + } +} + +func (s *MultiLineState) updateSpaceOnlyIndentColumn(column int) { + if s.firstLineIndentColumn != 0 { + return + } + s.spaceOnlyIndentColumn = column +} + +func (s *MultiLineState) validateIndentAfterSpaceOnly(column int) error { + if s.firstLineIndentColumn != 0 { + return nil + } + if s.spaceOnlyIndentColumn > column { + return errors.New("invalid number of indent is specified after space only") + } + return nil +} + +func (s *MultiLineState) validateIndentColumn() error { + if firstLineIndentColumnByOpt(s.opt) == 0 { + return nil + } + if s.firstLineIndentColumn > s.lineIndentColumn { + return errors.New("invalid number of indent is specified in the multi-line header") + } + return nil +} + +func (s *MultiLineState) updateNewLineState() { + s.prevLineIndentColumn = s.lineIndentColumn + if s.lineIndentColumn != 0 { + s.lastNotSpaceOnlyLineIndentColumn = s.lineIndentColumn + } + s.foldedNewLine = true + s.lineIndentColumn = 0 +} + +func (s *MultiLineState) isIndentColumn(column int) bool { + if s.firstLineIndentColumn == 0 { + return column == 1 + } + return s.firstLineIndentColumn > column +} + +func (s *MultiLineState) addIndent(ctx *Context, column int) { + if s.firstLineIndentColumn == 0 { + return + } + + // If the first line of the document has already been evaluated, the number is treated as the threshold, since the `firstLineIndentColumn` is a positive number. + if column < s.firstLineIndentColumn { + return + } + + // `c.foldedNewLine` is a variable that is set to true for every newline. + if !s.isLiteral && s.foldedNewLine { + s.foldedNewLine = false + } + // Since addBuf ignore space character, add to the buffer directly. + ctx.buf = append(ctx.buf, ' ') + ctx.notSpaceCharPos = len(ctx.buf) +} + +// updateNewLineInFolded if Folded or RawFolded context and the content on the current line starts at the same column as the previous line, +// treat the new-line-char as a space. +func (s *MultiLineState) updateNewLineInFolded(ctx *Context, column int) { + if s.isLiteral { + return + } + + // Folded or RawFolded. + + if !s.foldedNewLine { + return + } + var ( + lastChar rune + prevLastChar rune + ) + if len(ctx.buf) != 0 { + lastChar = ctx.buf[len(ctx.buf)-1] + } + if len(ctx.buf) > 1 { + prevLastChar = ctx.buf[len(ctx.buf)-2] + } + if s.lineIndentColumn == s.prevLineIndentColumn { + // --- + // > + // a + // b + if lastChar == '\n' { + ctx.buf[len(ctx.buf)-1] = ' ' + } + } else if s.prevLineIndentColumn == 0 && s.lastNotSpaceOnlyLineIndentColumn == column { + // if previous line is indent-space and new-line-char only, prevLineIndentColumn is zero. + // In this case, last new-line-char is removed. + // --- + // > + // a + // + // b + if lastChar == '\n' && prevLastChar == '\n' { + ctx.buf = ctx.buf[:len(ctx.buf)-1] + ctx.notSpaceCharPos = len(ctx.buf) + } + } + s.foldedNewLine = false +} + +func (s *MultiLineState) hasTrimAllEndNewlineOpt() bool { + return strings.HasPrefix(s.opt, "-") || strings.HasSuffix(s.opt, "-") || s.isRawFolded +} + +func (s *MultiLineState) hasKeepAllEndNewlineOpt() bool { + return strings.HasPrefix(s.opt, "+") || strings.HasSuffix(s.opt, "+") } func (c *Context) addToken(tk *token.Token) { @@ -90,7 +270,7 @@ func (c *Context) addToken(tk *token.Token) { } func (c *Context) addBuf(r rune) { - if len(c.buf) == 0 && r == ' ' { + if len(c.buf) == 0 && (r == ' ' || r == '\t') { return } c.buf = append(c.buf, r) @@ -99,6 +279,16 @@ func (c *Context) addBuf(r rune) { } } +func (c *Context) addBufWithTab(r rune) { + if len(c.buf) == 0 && r == ' ' { + return + } + c.buf = append(c.buf, r) + if r != ' ' { + c.notSpaceCharPos = len(c.buf) + } +} + func (c *Context) addOriginBuf(r rune) { c.obuf = append(c.obuf, r) if r != ' ' && r != '\t' { @@ -106,7 +296,7 @@ func (c *Context) addOriginBuf(r rune) { } } -func (c *Context) removeRightSpaceFromBuf() int { +func (c *Context) removeRightSpaceFromBuf() { trimmedBuf := c.obuf[:c.notSpaceOrgCharPos] buflen := len(trimmedBuf) diff := len(c.obuf) - buflen @@ -114,11 +304,6 @@ func (c *Context) removeRightSpaceFromBuf() int { c.obuf = c.obuf[:buflen] c.buf = c.bufferedSrc() } - return diff -} - -func (c *Context) isDocument() bool { - return c.isLiteral || c.isFolded || c.isRawFolded } func (c *Context) isEOS() bool { @@ -126,7 +311,7 @@ func (c *Context) isEOS() bool { } func (c *Context) isNextEOS() bool { - return len(c.src)-1 <= c.idx+1 + return len(c.src) <= c.idx+1 } func (c *Context) next() bool { @@ -151,18 +336,6 @@ func (c *Context) currentChar() rune { return rune(0) } -func (c *Context) currentCharWithSkipWhitespace() rune { - idx := c.idx - for c.size > idx { - ch := c.src[idx] - if ch != whitespace { - return ch - } - idx++ - } - return rune(0) -} - func (c *Context) nextChar() rune { if c.size > c.idx+1 { return c.src[c.idx+1] @@ -186,25 +359,52 @@ func (c *Context) progress(num int) { c.idx += num } -func (c *Context) nextPos() int { - return c.idx + 1 -} - func (c *Context) existsBuffer() bool { return len(c.bufferedSrc()) != 0 } +func (c *Context) isMultiLine() bool { + return c.mstate != nil +} + func (c *Context) bufferedSrc() []rune { src := c.buf[:c.notSpaceCharPos] - if c.isDocument() && c.literalOpt == "-" { - // remove end '\n' character and trailing empty lines + if c.isMultiLine() { + mstate := c.getMultiLineState() + // remove end '\n' character and trailing empty lines. // https://yaml.org/spec/1.2.2/#8112-block-chomping-indicator - for { - if len(src) > 0 && src[len(src)-1] == '\n' { - src = src[:len(src)-1] - continue + if mstate.hasTrimAllEndNewlineOpt() { + // If the '-' flag is specified, all trailing newline characters will be removed. + src = []rune(strings.TrimRight(string(src), "\n")) + } else if !mstate.hasKeepAllEndNewlineOpt() { + // Normally, all but one of the trailing newline characters are removed. + var newLineCharCount int + for i := len(src) - 1; i >= 0; i-- { + if src[i] == '\n' { + newLineCharCount++ + continue + } + break } - break + removedNewLineCharCount := newLineCharCount - 1 + for removedNewLineCharCount > 0 { + src = []rune(strings.TrimSuffix(string(src), "\n")) + removedNewLineCharCount-- + } + } + + // If the text ends with a space character, remove all of them. + if mstate.hasTrimAllEndNewlineOpt() { + src = []rune(strings.TrimRight(string(src), " ")) + } + if string(src) == "\n" { + // If the content consists only of a newline, + // it can be considered as the document ending without any specified value, + // so it is treated as an empty string. + src = []rune{} + } + if mstate.hasKeepAllEndNewlineOpt() && len(src) == 0 { + src = []rune{'\n'} } } return src @@ -216,18 +416,34 @@ func (c *Context) bufferedToken(pos *token.Position) *token.Token { } source := c.bufferedSrc() if len(source) == 0 { + c.buf = c.buf[:0] // clear value's buffer only. return nil } var tk *token.Token - if c.isDocument() { + if c.isMultiLine() { tk = token.String(string(source), string(c.obuf), pos) } else { tk = token.New(string(source), string(c.obuf), pos) } + c.setTokenTypeByPrevTag(tk) c.resetBuffer() return tk } +func (c *Context) setTokenTypeByPrevTag(tk *token.Token) { + lastTk := c.lastToken() + if lastTk == nil { + return + } + if lastTk.Type != token.TagType { + return + } + tag := token.ReservedTagKeyword(lastTk.Value) + if _, exists := token.ReservedTagKeywordMap[tag]; !exists { + tk.Type = token.StringType + } +} + func (c *Context) lastToken() *token.Token { if len(c.tokens) != 0 { return c.tokens[len(c.tokens)-1] diff --git a/vendor/github.com/goccy/go-yaml/scanner/error.go b/vendor/github.com/goccy/go-yaml/scanner/error.go new file mode 100644 index 000000000..3f5419af5 --- /dev/null +++ b/vendor/github.com/goccy/go-yaml/scanner/error.go @@ -0,0 +1,17 @@ +package scanner + +import "github.com/goccy/go-yaml/token" + +type InvalidTokenError struct { + Token *token.Token +} + +func (e *InvalidTokenError) Error() string { + return e.Token.Error +} + +func ErrInvalidToken(tk *token.Token) *InvalidTokenError { + return &InvalidTokenError{ + Token: tk, + } +} diff --git a/vendor/github.com/goccy/go-yaml/scanner/scanner.go b/vendor/github.com/goccy/go-yaml/scanner/scanner.go index 77acb4184..13a7ecc4d 100644 --- a/vendor/github.com/goccy/go-yaml/scanner/scanner.go +++ b/vendor/github.com/goccy/go-yaml/scanner/scanner.go @@ -1,11 +1,12 @@ package scanner import ( + "errors" + "fmt" "io" + "strconv" "strings" - "golang.org/x/xerrors" - "github.com/goccy/go-yaml/token" ) @@ -26,20 +27,27 @@ 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 isFirstCharAtLine bool isAnchor bool + isAlias bool + isDirective bool startedFlowSequenceNum int startedFlowMapNum int indentState IndentState @@ -65,7 +73,7 @@ func (s *Scanner) bufferedToken(ctx *Context) *token.Token { line := s.line column := s.column - len(ctx.buf) level := s.indentLevel - if ctx.isSaveIndentMode() { + if ctx.isMultiLine() { line -= s.newLineCount(ctx.buf) column = strings.Index(string(ctx.obuf), string(ctx.buf)) + 1 // Since we are in a literal, folded or raw folded @@ -87,30 +95,30 @@ func (s *Scanner) bufferedToken(ctx *Context) *token.Token { func (s *Scanner) progressColumn(ctx *Context, num int) { s.column += num s.offset += num - ctx.progress(num) + s.progress(ctx, num) +} + +func (s *Scanner) progressOnly(ctx *Context, num int) { + s.offset += num + s.progress(ctx, num) } func (s *Scanner) progressLine(ctx *Context) { + s.prevLineIndentNum = s.indentNum s.column = 1 s.line++ s.offset++ s.indentNum = 0 s.isFirstCharAtLine = true s.isAnchor = false - ctx.progress(1) + s.isAlias = false + s.isDirective = false + s.progress(ctx, 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) progress(ctx *Context, num int) { + ctx.progress(num) + s.sourcePos += num } func (s *Scanner) isNewLineChar(c rune) bool { @@ -141,67 +149,50 @@ 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-- } } +} - if s.prevIndentColumn > 0 { - if s.prevIndentColumn < s.column { - s.indentState = IndentStateUp - } else if s.prevIndentColumn != s.column || indentNumBasedIndentState != IndentStateEqual { - // The following case ( current position is 'd' ), some variables becomes like here - // - 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. - // --- - // a: - // b - // c - // d: e - // ^ - s.indentState = IndentStateDown - } else { - s.indentState = IndentStateEqual - } +func (s *Scanner) updateIndentState(ctx *Context) { + if s.lastDelimColumn == 0 { + return + } + + if s.lastDelimColumn < s.column { + s.indentState = IndentStateUp } else { - s.indentState = indentNumBasedIndentState + // If lastDelimColumn and s.column are the same, + // treat as Down state since it is the same column as delimiter. + s.indentState = IndentStateDown } } func (s *Scanner) updateIndent(ctx *Context, c rune) { - if s.isFirstCharAtLine && s.isNewLineChar(c) && ctx.isDocument() { + if s.isFirstCharAtLine && s.isNewLineChar(c) { return } if s.isFirstCharAtLine && c == ' ' { s.indentNum++ return } + if s.isFirstCharAtLine && c == '\t' { + // found tab indent. + // In this case, scanTab returns error. + return + } if !s.isFirstCharAtLine { s.indentState = IndentStateKeep return } + s.updateIndentLevel() 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 { @@ -212,20 +203,15 @@ func (s *Scanner) isChangedToIndentStateUp() bool { return s.indentState == IndentStateUp } -func (s *Scanner) isChangedToIndentStateEqual() bool { - return s.indentState == IndentStateEqual -} - func (s *Scanner) addBufferedTokenIfExists(ctx *Context) { ctx.addToken(s.bufferedToken(ctx)) } -func (s *Scanner) breakLiteral(ctx *Context) { - s.docStartColumn = 0 - ctx.breakLiteral() +func (s *Scanner) breakMultiLine(ctx *Context) { + ctx.breakMultiLine() } -func (s *Scanner) scanSingleQuote(ctx *Context) (tk *token.Token, pos int) { +func (s *Scanner) scanSingleQuote(ctx *Context) (*token.Token, error) { ctx.addOriginBuf('\'') srcpos := s.pos() startIndex := ctx.idx + 1 @@ -234,6 +220,7 @@ func (s *Scanner) scanSingleQuote(ctx *Context) (tk *token.Token, pos int) { value := []rune{} isFirstLineChar := false isNewLine := false + for idx := startIndex; idx < size; idx++ { if !isNewLine { s.progressColumn(ctx, 1) @@ -241,34 +228,67 @@ func (s *Scanner) scanSingleQuote(ctx *Context) (tk *token.Token, pos int) { isNewLine = false } c := src[idx] - pos = idx + 1 ctx.addOriginBuf(c) if s.isNewLineChar(c) { - value = append(value, ' ') + notSpaceIdx := -1 + for i := len(value) - 1; i >= 0; i-- { + if value[i] == ' ' { + continue + } + notSpaceIdx = i + break + } + if len(value) > notSpaceIdx { + value = value[:notSpaceIdx+1] + } + if isFirstLineChar { + value = append(value, '\n') + } else { + value = append(value, ' ') + } isFirstLineChar = true isNewLine = true s.progressLine(ctx) + if idx+1 < size { + if err := s.validateDocumentSeparatorMarker(ctx, src[idx+1:]); err != nil { + return nil, err + } + } continue - } else if c == ' ' && isFirstLineChar { + } else if isFirstLineChar && c == ' ' { + continue + } else if isFirstLineChar && c == '\t' { + if s.lastDelimColumn >= s.column { + return nil, ErrInvalidToken( + token.Invalid( + "tab character cannot be used for indentation in single-quoted text", + string(ctx.obuf), s.pos(), + ), + ) + } continue } else if c != '\'' { value = append(value, c) isFirstLineChar = false continue - } - if idx+1 < len(ctx.src) && ctx.src[idx+1] == '\'' { + } else if idx+1 < len(ctx.src) && ctx.src[idx+1] == '\'' { // '' handle as ' character value = append(value, c) ctx.addOriginBuf(c) idx++ + s.progressColumn(ctx, 1) continue } s.progressColumn(ctx, 1) - tk = token.SingleQuote(string(value), string(ctx.obuf), srcpos) - pos = idx - startIndex + 1 - return + return token.SingleQuote(string(value), string(ctx.obuf), srcpos), nil } - return + s.progressColumn(ctx, 1) + return nil, ErrInvalidToken( + token.Invalid( + "could not find end character of single-quoted text", + string(ctx.obuf), srcpos, + ), + ) } func hexToInt(b rune) int { @@ -289,7 +309,7 @@ func hexRunesToInt(b []rune) int { return sum } -func (s *Scanner) scanDoubleQuote(ctx *Context) (tk *token.Token, pos int) { +func (s *Scanner) scanDoubleQuote(ctx *Context) (*token.Token, error) { ctx.addOriginBuf('"') srcpos := s.pos() startIndex := ctx.idx + 1 @@ -298,6 +318,7 @@ func (s *Scanner) scanDoubleQuote(ctx *Context) (tk *token.Token, pos int) { value := []rune{} isFirstLineChar := false isNewLine := false + for idx := startIndex; idx < size; idx++ { if !isNewLine { s.progressColumn(ctx, 1) @@ -305,15 +326,44 @@ func (s *Scanner) scanDoubleQuote(ctx *Context) (tk *token.Token, pos int) { isNewLine = false } c := src[idx] - pos = idx + 1 ctx.addOriginBuf(c) if s.isNewLineChar(c) { - value = append(value, ' ') + notSpaceIdx := -1 + for i := len(value) - 1; i >= 0; i-- { + if value[i] == ' ' { + continue + } + notSpaceIdx = i + break + } + if len(value) > notSpaceIdx { + value = value[:notSpaceIdx+1] + } + if isFirstLineChar { + value = append(value, '\n') + } else { + value = append(value, ' ') + } isFirstLineChar = true isNewLine = true s.progressLine(ctx) + if idx+1 < size { + if err := s.validateDocumentSeparatorMarker(ctx, src[idx+1:]); err != nil { + return nil, err + } + } continue - } else if c == ' ' && isFirstLineChar { + } else if isFirstLineChar && c == ' ' { + continue + } else if isFirstLineChar && c == '\t' { + if s.lastDelimColumn >= s.column { + return nil, ErrInvalidToken( + token.Invalid( + "tab character cannot be used for indentation in double-quoted text", + string(ctx.obuf), s.pos(), + ), + ) + } continue } else if c == '\\' { isFirstLineChar = false @@ -324,105 +374,292 @@ func (s *Scanner) scanDoubleQuote(ctx *Context) (tk *token.Token, pos int) { nextChar := src[idx+1] progress := 0 switch nextChar { + case '0': + progress = 1 + ctx.addOriginBuf(nextChar) + value = append(value, 0x00) + case 'a': + progress = 1 + ctx.addOriginBuf(nextChar) + value = append(value, 0x07) case 'b': progress = 1 ctx.addOriginBuf(nextChar) - value = append(value, '\b') - case 'e': + value = append(value, 0x08) + case 't': progress = 1 ctx.addOriginBuf(nextChar) - value = append(value, '\x1B') - case 'f': - progress = 1 - ctx.addOriginBuf(nextChar) - value = append(value, '\f') + value = append(value, 0x09) case 'n': progress = 1 ctx.addOriginBuf(nextChar) - value = append(value, '\n') - case 'r': - progress = 1 - ctx.addOriginBuf(nextChar) - value = append(value, '\r') + value = append(value, 0x0A) case 'v': progress = 1 ctx.addOriginBuf(nextChar) - value = append(value, '\v') - case 'L': // LS (#x2028) + value = append(value, 0x0B) + case 'f': progress = 1 ctx.addOriginBuf(nextChar) - value = append(value, []rune{'\xE2', '\x80', '\xA8'}...) - case 'N': // NEL (#x85) + value = append(value, 0x0C) + case 'r': progress = 1 ctx.addOriginBuf(nextChar) - value = append(value, []rune{'\xC2', '\x85'}...) - case 'P': // PS (#x2029) + value = append(value, 0x0D) + case 'e': progress = 1 ctx.addOriginBuf(nextChar) - value = append(value, []rune{'\xE2', '\x80', '\xA9'}...) - case '_': // #xA0 + value = append(value, 0x1B) + case ' ': progress = 1 ctx.addOriginBuf(nextChar) - value = append(value, []rune{'\xC2', '\xA0'}...) + value = append(value, 0x20) case '"': progress = 1 ctx.addOriginBuf(nextChar) - value = append(value, nextChar) - case 'x': - progress = 3 - if idx+progress >= size { - // TODO: need to return error - //err = xerrors.New("invalid escape character \\x") - return - } - codeNum := hexRunesToInt(src[idx+2 : idx+progress+1]) - value = append(value, rune(codeNum)) - case 'u': - progress = 5 - if idx+progress >= size { - // TODO: need to return error - //err = xerrors.New("invalid escape character \\u") - return - } - codeNum := hexRunesToInt(src[idx+2 : idx+progress+1]) - value = append(value, rune(codeNum)) - case 'U': - progress = 9 - if idx+progress >= size { - // TODO: need to return error - //err = xerrors.New("invalid escape character \\U") - return - } - codeNum := hexRunesToInt(src[idx+2 : idx+progress+1]) - value = append(value, rune(codeNum)) + value = append(value, 0x22) + case '/': + progress = 1 + ctx.addOriginBuf(nextChar) + value = append(value, 0x2F) case '\\': progress = 1 ctx.addOriginBuf(nextChar) - value = append(value, c) + value = append(value, 0x5C) + case 'N': + progress = 1 + ctx.addOriginBuf(nextChar) + value = append(value, 0x85) + case '_': + progress = 1 + ctx.addOriginBuf(nextChar) + value = append(value, 0xA0) + case 'L': + progress = 1 + ctx.addOriginBuf(nextChar) + value = append(value, 0x2028) + case 'P': + progress = 1 + ctx.addOriginBuf(nextChar) + value = append(value, 0x2029) + case 'x': + if idx+3 >= size { + progress = 1 + ctx.addOriginBuf(nextChar) + value = append(value, nextChar) + } else { + progress = 3 + codeNum := hexRunesToInt(src[idx+2 : idx+progress+1]) + value = append(value, rune(codeNum)) + } + case 'u': + // \u0000 style must have 5 characters at least. + if idx+5 >= size { + return nil, ErrInvalidToken( + token.Invalid( + "not enough length for escaped UTF-16 character", + string(ctx.obuf), s.pos(), + ), + ) + } + progress = 5 + codeNum := hexRunesToInt(src[idx+2 : idx+6]) + + // handle surrogate pairs. + if codeNum >= 0xD800 && codeNum <= 0xDBFF { + high := codeNum + + // \u0000\u0000 style must have 11 characters at least. + if idx+11 >= size { + return nil, ErrInvalidToken( + token.Invalid( + "not enough length for escaped UTF-16 surrogate pair", + string(ctx.obuf), s.pos(), + ), + ) + } + + if src[idx+6] != '\\' || src[idx+7] != 'u' { + return nil, ErrInvalidToken( + token.Invalid( + "found unexpected character after high surrogate for UTF-16 surrogate pair", + string(ctx.obuf), s.pos(), + ), + ) + } + + low := hexRunesToInt(src[idx+8 : idx+12]) + if low < 0xDC00 || low > 0xDFFF { + return nil, ErrInvalidToken( + token.Invalid( + "found unexpected low surrogate after high surrogate", + string(ctx.obuf), s.pos(), + ), + ) + } + codeNum = ((high - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000 + progress += 6 + } + value = append(value, rune(codeNum)) + case 'U': + // \U00000000 style must have 9 characters at least. + if idx+9 >= size { + return nil, ErrInvalidToken( + token.Invalid( + "not enough length for escaped UTF-32 character", + string(ctx.obuf), s.pos(), + ), + ) + } + progress = 9 + codeNum := hexRunesToInt(src[idx+2 : idx+10]) + value = append(value, rune(codeNum)) + case '\n': + isFirstLineChar = true + isNewLine = true + ctx.addOriginBuf(nextChar) + s.progressColumn(ctx, 1) + s.progressLine(ctx) + idx++ + continue + case '\r': + isFirstLineChar = true + isNewLine = true + ctx.addOriginBuf(nextChar) + s.progressLine(ctx) + progress = 1 + // Skip \n after \r in CRLF sequences + if idx+2 < size && src[idx+2] == '\n' { + ctx.addOriginBuf('\n') + progress = 2 + } + case '\t': + progress = 1 + ctx.addOriginBuf(nextChar) + value = append(value, nextChar) default: - value = append(value, c) + s.progressColumn(ctx, 1) + return nil, ErrInvalidToken( + token.Invalid( + fmt.Sprintf("found unknown escape character %q", nextChar), + string(ctx.obuf), s.pos(), + ), + ) } idx += progress s.progressColumn(ctx, progress) continue + } else if c == '\t' { + var ( + foundNotSpaceChar bool + progress int + ) + for i := idx + 1; i < size; i++ { + if src[i] == ' ' || src[i] == '\t' { + progress++ + continue + } + if s.isNewLineChar(src[i]) { + break + } + foundNotSpaceChar = true + } + if foundNotSpaceChar { + value = append(value, c) + if src[idx+1] != '"' { + s.progressColumn(ctx, 1) + } + } else { + idx += progress + s.progressColumn(ctx, progress) + } + continue } else if c != '"' { value = append(value, c) isFirstLineChar = false continue } s.progressColumn(ctx, 1) - tk = token.DoubleQuote(string(value), string(ctx.obuf), srcpos) - pos = idx - startIndex + 1 - return + return token.DoubleQuote(string(value), string(ctx.obuf), srcpos), nil } - return + s.progressColumn(ctx, 1) + return nil, ErrInvalidToken( + token.Invalid( + "could not find end character of double-quoted text", + string(ctx.obuf), srcpos, + ), + ) } -func (s *Scanner) scanQuote(ctx *Context, ch rune) (tk *token.Token, pos int) { - if ch == '\'' { - return s.scanSingleQuote(ctx) +func (s *Scanner) validateDocumentSeparatorMarker(ctx *Context, src []rune) error { + if s.foundDocumentSeparatorMarker(src) { + return ErrInvalidToken( + token.Invalid("found unexpected document separator", string(ctx.obuf), s.pos()), + ) } - return s.scanDoubleQuote(ctx) + return nil +} + +func (s *Scanner) foundDocumentSeparatorMarker(src []rune) bool { + if len(src) < 3 { + return false + } + var marker string + if len(src) == 3 { + marker = string(src) + } else { + marker = strings.TrimRightFunc(string(src[:4]), func(r rune) bool { + return r == ' ' || r == '\t' || r == '\n' || r == '\r' + }) + } + return marker == "---" || marker == "..." +} + +func (s *Scanner) scanQuote(ctx *Context, ch rune) (bool, error) { + if ctx.existsBuffer() { + return false, nil + } + if ch == '\'' { + tk, err := s.scanSingleQuote(ctx) + if err != nil { + return false, err + } + ctx.addToken(tk) + } else { + tk, err := s.scanDoubleQuote(ctx) + if err != nil { + return false, err + } + ctx.addToken(tk) + } + ctx.clear() + return true, nil +} + +func (s *Scanner) scanWhiteSpace(ctx *Context) bool { + if ctx.isMultiLine() { + return false + } + if !s.isAnchor && !s.isDirective && !s.isAlias && !s.isFirstCharAtLine { + return false + } + + if s.isFirstCharAtLine { + s.progressColumn(ctx, 1) + ctx.addOriginBuf(' ') + return true + } + if s.isDirective { + s.addBufferedTokenIfExists(ctx) + s.progressColumn(ctx, 1) + ctx.addOriginBuf(' ') + return true + } + + s.addBufferedTokenIfExists(ctx) + s.isAnchor = false + s.isAlias = false + return true } func (s *Scanner) isMergeKey(ctx *Context) bool { @@ -449,156 +686,169 @@ func (s *Scanner) isMergeKey(ctx *Context) bool { return false } -func (s *Scanner) scanTag(ctx *Context) (tk *token.Token, pos int) { +func (s *Scanner) scanTag(ctx *Context) (bool, error) { + if ctx.existsBuffer() || s.isDirective { + return false, nil + } + ctx.addOriginBuf('!') - ctx.progress(1) // skip '!' character + s.progress(ctx, 1) // skip '!' character + + var progress int for idx, c := range ctx.src[ctx.idx:] { - pos = idx + 1 - ctx.addOriginBuf(c) + progress = idx + 1 switch c { - case ' ', '\n', '\r': + case ' ': + ctx.addOriginBuf(c) value := ctx.source(ctx.idx-1, ctx.idx+idx) - tk = token.Tag(value, string(ctx.obuf), s.pos()) - pos = len([]rune(value)) - return + ctx.addToken(token.Tag(value, string(ctx.obuf), s.pos())) + s.progressColumn(ctx, len([]rune(value))) + ctx.clear() + return true, nil + case ',': + if s.startedFlowSequenceNum > 0 || s.startedFlowMapNum > 0 { + value := ctx.source(ctx.idx-1, ctx.idx+idx) + ctx.addToken(token.Tag(value, string(ctx.obuf), s.pos())) + s.progressColumn(ctx, len([]rune(value))-1) // progress column before collect-entry for scanning it at scanFlowEntry function. + ctx.clear() + return true, nil + } else { + ctx.addOriginBuf(c) + } + case '\n', '\r': + ctx.addOriginBuf(c) + value := ctx.source(ctx.idx-1, ctx.idx+idx) + ctx.addToken(token.Tag(value, string(ctx.obuf), s.pos())) + s.progressColumn(ctx, len([]rune(value))-1) // progress column before new-line-char for scanning new-line-char at scanNewLine function. + ctx.clear() + return true, nil + case '{', '}': + ctx.addOriginBuf(c) + s.progressColumn(ctx, progress) + invalidTk := token.Invalid(fmt.Sprintf("found invalid tag character %q", c), string(ctx.obuf), s.pos()) + return false, ErrInvalidToken(invalidTk) + default: + ctx.addOriginBuf(c) } } - return + s.progressColumn(ctx, progress) + ctx.clear() + return true, nil } -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': - if ctx.previousChar() == '\\' { - continue - } - value := ctx.source(ctx.idx, ctx.idx+idx) - tk = token.Comment(value, string(ctx.obuf), s.pos()) - pos = len([]rune(value)) + 1 - return +func (s *Scanner) scanComment(ctx *Context) bool { + if ctx.existsBuffer() { + c := ctx.previousChar() + if c != ' ' && c != '\t' && !s.isNewLineChar(c) { + return false } } + + s.addBufferedTokenIfExists(ctx) + ctx.addOriginBuf('#') + s.progress(ctx, 1) // skip '#' character + + for idx, c := range ctx.src[ctx.idx:] { + ctx.addOriginBuf(c) + if !s.isNewLineChar(c) { + continue + } + if ctx.previousChar() == '\\' { + continue + } + value := ctx.source(ctx.idx, ctx.idx+idx) + progress := len([]rune(value)) + ctx.addToken(token.Comment(value, string(ctx.obuf), s.pos())) + s.progressColumn(ctx, progress) + s.progressLine(ctx) + ctx.clear() + return true + } // document ends with comment. value := string(ctx.src[ctx.idx:]) - tk = token.Comment(value, string(ctx.obuf), s.pos()) - pos = len([]rune(value)) + 1 - return + ctx.addToken(token.Comment(value, string(ctx.obuf), s.pos())) + progress := len([]rune(value)) + s.progressColumn(ctx, progress) + s.progressLine(ctx) + ctx.clear() + return true } -func trimCommentFromLiteralOpt(text string) (string, error) { - idx := strings.Index(text, "#") - if idx < 0 { - return text, nil - } - if idx == 0 { - return "", xerrors.New("invalid literal header") - } - return text[:idx-1], nil -} - -func (s *Scanner) scanLiteral(ctx *Context, c rune) { +func (s *Scanner) scanMultiLine(ctx *Context, c rune) error { + state := ctx.getMultiLineState() ctx.addOriginBuf(c) if ctx.isEOS() { - if ctx.isLiteral { + if s.isFirstCharAtLine && c == ' ' { + state.addIndent(ctx, s.column) + } else { ctx.addBuf(c) } + state.updateIndentColumn(s.column) + if err := state.validateIndentColumn(); err != nil { + invalidTk := token.Invalid(err.Error(), string(ctx.obuf), s.pos()) + s.progressColumn(ctx, 1) + return ErrInvalidToken(invalidTk) + } value := ctx.bufferedSrc() ctx.addToken(token.String(string(value), string(ctx.obuf), s.pos())) - ctx.resetBuffer() + ctx.clear() s.progressColumn(ctx, 1) } else if s.isNewLineChar(c) { - if ctx.isLiteral { - ctx.addBuf(c) - } else { - ctx.addBuf(' ') - } + ctx.addBuf(c) + state.updateSpaceOnlyIndentColumn(s.column - 1) + state.updateNewLineState() s.progressLine(ctx) - } else if s.isFirstCharAtLine && c == ' ' { - if 0 < s.docStartColumn && s.docStartColumn <= s.column { - ctx.addBuf(c) + if ctx.next() { + if s.foundDocumentSeparatorMarker(ctx.src[ctx.idx:]) { + value := ctx.bufferedSrc() + ctx.addToken(token.String(string(value), string(ctx.obuf), s.pos())) + ctx.clear() + s.breakMultiLine(ctx) + } } + } else if s.isFirstCharAtLine && c == ' ' { + state.addIndent(ctx, s.column) + s.progressColumn(ctx, 1) + } else if s.isFirstCharAtLine && c == '\t' && state.isIndentColumn(s.column) { + err := ErrInvalidToken( + token.Invalid( + "found a tab character where an indentation space is expected", + string(ctx.obuf), s.pos(), + ), + ) + s.progressColumn(ctx, 1) + return err + } else if c == '\t' && !state.isIndentColumn(s.column) { + ctx.addBufWithTab(c) s.progressColumn(ctx, 1) } else { - if s.docStartColumn == 0 { - s.docStartColumn = s.column + if err := state.validateIndentAfterSpaceOnly(s.column); err != nil { + invalidTk := token.Invalid(err.Error(), string(ctx.obuf), s.pos()) + s.progressColumn(ctx, 1) + return ErrInvalidToken(invalidTk) } - ctx.addBuf(c) + state.updateIndentColumn(s.column) + if err := state.validateIndentColumn(); err != nil { + invalidTk := token.Invalid(err.Error(), string(ctx.obuf), s.pos()) + s.progressColumn(ctx, 1) + return ErrInvalidToken(invalidTk) + } + if col := state.lastDelimColumn(); col > 0 { + s.lastDelimColumn = col + } + state.updateNewLineInFolded(ctx, s.column) + ctx.addBufWithTab(c) s.progressColumn(ctx, 1) } -} - -func (s *Scanner) scanLiteralHeader(ctx *Context) (pos int, err error) { - header := ctx.currentChar() - ctx.addOriginBuf(header) - ctx.progress(1) // skip '|' or '>' character - for idx, c := range ctx.src[ctx.idx:] { - pos = idx - ctx.addOriginBuf(c) - switch c { - case '\n', '\r': - value := ctx.source(ctx.idx, ctx.idx+idx) - opt := strings.TrimRight(value, " ") - orgOptLen := len(opt) - opt, err = trimCommentFromLiteralOpt(opt) - if err != nil { - return - } - switch opt { - case "", "+", "-", - "0", "1", "2", "3", "4", "5", "6", "7", "8", "9": - hasComment := len(opt) < orgOptLen - if header == '|' { - if hasComment { - commentLen := orgOptLen - len(opt) - headerPos := strings.Index(string(ctx.obuf), "|") - litBuf := ctx.obuf[:len(ctx.obuf)-commentLen-headerPos] - commentBuf := ctx.obuf[len(litBuf):] - ctx.addToken(token.Literal("|"+opt, string(litBuf), s.pos())) - s.column += len(litBuf) - s.offset += len(litBuf) - commentHeader := strings.Index(value, "#") - ctx.addToken(token.Comment(string(value[commentHeader+1:]), string(commentBuf), s.pos())) - } else { - ctx.addToken(token.Literal("|"+opt, string(ctx.obuf), s.pos())) - } - ctx.isLiteral = true - } else if header == '>' { - if hasComment { - commentLen := orgOptLen - len(opt) - headerPos := strings.Index(string(ctx.obuf), ">") - foldedBuf := ctx.obuf[:len(ctx.obuf)-commentLen-headerPos] - commentBuf := ctx.obuf[len(foldedBuf):] - ctx.addToken(token.Folded(">"+opt, string(foldedBuf), s.pos())) - s.column += len(foldedBuf) - s.offset += len(foldedBuf) - commentHeader := strings.Index(value, "#") - ctx.addToken(token.Comment(string(value[commentHeader+1:]), string(commentBuf), s.pos())) - } else { - ctx.addToken(token.Folded(">"+opt, string(ctx.obuf), s.pos())) - } - ctx.isFolded = true - } - s.indentState = IndentStateKeep - ctx.resetBuffer() - ctx.literalOpt = opt - return - } - break - } - } - err = xerrors.New("invalid literal header") - return + return nil } func (s *Scanner) scanNewLine(ctx *Context, c rune) { if len(ctx.buf) > 0 && s.savedPos == nil { + bufLen := len(ctx.bufferedSrc()) s.savedPos = s.pos() - s.savedPos.Column -= len(ctx.bufferedSrc()) + s.savedPos.Column -= bufLen + s.savedPos.Offset -= bufLen } // if the following case, origin buffer has unnecessary two spaces. @@ -606,14 +856,7 @@ func (s *Scanner) scanNewLine(ctx *Context, c rune) { // --- // a:[space][space] // b: c - removedNum := ctx.removeRightSpaceFromBuf() - if removedNum > 0 { - s.column -= removedNum - s.offset -= removedNum - if s.savedPos != nil { - s.savedPos.Column -= removedNum - } - } + ctx.removeRightSpaceFromBuf() // There is no problem that we ignore CR which followed by LF and normalize it to LF, because of following YAML1.2 spec. // > Line breaks inside scalar content must be normalized by the YAML processor. Each such line break must be parsed into a single line feed character. @@ -621,254 +864,613 @@ func (s *Scanner) scanNewLine(ctx *Context, c rune) { // > -- https://yaml.org/spec/1.2/spec.html if c == '\r' && ctx.nextChar() == '\n' { ctx.addOriginBuf('\r') - ctx.progress(1) + s.progress(ctx, 1) + s.offset++ c = '\n' } if ctx.isEOS() { s.addBufferedTokenIfExists(ctx) - } else if s.isAnchor { + } else if s.isAnchor || s.isAlias || s.isDirective { s.addBufferedTokenIfExists(ctx) } - ctx.addBuf(' ') + if ctx.existsBuffer() && s.isFirstCharAtLine { + if ctx.buf[len(ctx.buf)-1] == ' ' { + ctx.buf[len(ctx.buf)-1] = '\n' + } else { + ctx.buf = append(ctx.buf, '\n') + } + } else { + ctx.addBuf(' ') + } ctx.addOriginBuf(c) - ctx.isSingleLine = false s.progressLine(ctx) } -func (s *Scanner) scan(ctx *Context) (pos int) { +func (s *Scanner) isFlowMode() bool { + if s.startedFlowSequenceNum > 0 { + return true + } + if s.startedFlowMapNum > 0 { + return true + } + return false +} + +func (s *Scanner) scanFlowMapStart(ctx *Context) bool { + if ctx.existsBuffer() && !s.isFlowMode() { + return false + } + + s.addBufferedTokenIfExists(ctx) + ctx.addOriginBuf('{') + ctx.addToken(token.MappingStart(string(ctx.obuf), s.pos())) + s.startedFlowMapNum++ + s.progressColumn(ctx, 1) + ctx.clear() + return true +} + +func (s *Scanner) scanFlowMapEnd(ctx *Context) bool { + if s.startedFlowMapNum <= 0 { + return false + } + + s.addBufferedTokenIfExists(ctx) + ctx.addOriginBuf('}') + ctx.addToken(token.MappingEnd(string(ctx.obuf), s.pos())) + s.startedFlowMapNum-- + s.progressColumn(ctx, 1) + ctx.clear() + return true +} + +func (s *Scanner) scanFlowArrayStart(ctx *Context) bool { + if ctx.existsBuffer() && !s.isFlowMode() { + return false + } + + s.addBufferedTokenIfExists(ctx) + ctx.addOriginBuf('[') + ctx.addToken(token.SequenceStart(string(ctx.obuf), s.pos())) + s.startedFlowSequenceNum++ + s.progressColumn(ctx, 1) + ctx.clear() + return true +} + +func (s *Scanner) scanFlowArrayEnd(ctx *Context) bool { + if ctx.existsBuffer() && s.startedFlowSequenceNum <= 0 { + return false + } + + s.addBufferedTokenIfExists(ctx) + ctx.addOriginBuf(']') + ctx.addToken(token.SequenceEnd(string(ctx.obuf), s.pos())) + s.startedFlowSequenceNum-- + s.progressColumn(ctx, 1) + ctx.clear() + return true +} + +func (s *Scanner) scanFlowEntry(ctx *Context, c rune) bool { + if s.startedFlowSequenceNum <= 0 && s.startedFlowMapNum <= 0 { + return false + } + + s.addBufferedTokenIfExists(ctx) + ctx.addOriginBuf(c) + ctx.addToken(token.CollectEntry(string(ctx.obuf), s.pos())) + s.progressColumn(ctx, 1) + ctx.clear() + return true +} + +func (s *Scanner) scanMapDelim(ctx *Context) (bool, error) { + nc := ctx.nextChar() + if s.isDirective || s.isAnchor || s.isAlias { + return false, nil + } + if s.startedFlowMapNum <= 0 && nc != ' ' && nc != '\t' && !s.isNewLineChar(nc) && !ctx.isNextEOS() { + return false, nil + } + if s.startedFlowMapNum > 0 && nc == '/' { + // like http:// + return false, nil + } + if s.startedFlowMapNum > 0 { + tk := ctx.lastToken() + if tk != nil && tk.Type == token.MappingValueType { + return false, nil + } + } + + if strings.HasPrefix(strings.TrimPrefix(string(ctx.obuf), " "), "\t") && !strings.HasPrefix(string(ctx.buf), "\t") { + invalidTk := token.Invalid("tab character cannot use as a map key directly", string(ctx.obuf), s.pos()) + s.progressColumn(ctx, 1) + return false, ErrInvalidToken(invalidTk) + } + + // mapping value + tk := s.bufferedToken(ctx) + if tk != nil { + 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.lastDelimColumn = tk.Position.Column + } + } + ctx.addToken(token.MappingValue(s.pos())) + s.progressColumn(ctx, 1) + ctx.clear() + return true, nil +} + +func (s *Scanner) scanDocumentStart(ctx *Context) bool { + if s.indentNum != 0 { + return false + } + if s.column != 1 { + return false + } + if ctx.repeatNum('-') != 3 { + return false + } + if ctx.size > ctx.idx+3 { + c := ctx.src[ctx.idx+3] + if c != ' ' && c != '\t' && c != '\n' && c != '\r' { + return false + } + } + + s.addBufferedTokenIfExists(ctx) + ctx.addToken(token.DocumentHeader(string(ctx.obuf)+"---", s.pos())) + s.progressColumn(ctx, 3) + ctx.clear() + s.clearState() + return true +} + +func (s *Scanner) scanDocumentEnd(ctx *Context) bool { + if s.indentNum != 0 { + return false + } + if s.column != 1 { + return false + } + if ctx.repeatNum('.') != 3 { + return false + } + + s.addBufferedTokenIfExists(ctx) + ctx.addToken(token.DocumentEnd(string(ctx.obuf)+"...", s.pos())) + s.progressColumn(ctx, 3) + ctx.clear() + return true +} + +func (s *Scanner) scanMergeKey(ctx *Context) bool { + if !s.isMergeKey(ctx) { + return false + } + + s.lastDelimColumn = s.column + ctx.addToken(token.MergeKey(string(ctx.obuf)+"<<", s.pos())) + s.progressColumn(ctx, 2) + ctx.clear() + return true +} + +func (s *Scanner) scanRawFoldedChar(ctx *Context) bool { + if !ctx.existsBuffer() { + return false + } + if !s.isChangedToIndentStateUp() { + return false + } + + ctx.setRawFolded(s.column) + ctx.addBuf('-') + ctx.addOriginBuf('-') + s.progressColumn(ctx, 1) + return true +} + +func (s *Scanner) scanSequence(ctx *Context) (bool, error) { + if ctx.existsBuffer() { + return false, nil + } + + nc := ctx.nextChar() + if nc != 0 && nc != ' ' && nc != '\t' && !s.isNewLineChar(nc) { + return false, nil + } + + if strings.HasPrefix(strings.TrimPrefix(string(ctx.obuf), " "), "\t") { + invalidTk := token.Invalid("tab character cannot use as a sequence delimiter", string(ctx.obuf), s.pos()) + s.progressColumn(ctx, 1) + return false, ErrInvalidToken(invalidTk) + } + + s.addBufferedTokenIfExists(ctx) + ctx.addOriginBuf('-') + tk := token.SequenceEntry(string(ctx.obuf), s.pos()) + s.lastDelimColumn = tk.Position.Column + ctx.addToken(tk) + s.progressColumn(ctx, 1) + ctx.clear() + return true, nil +} + +func (s *Scanner) scanMultiLineHeader(ctx *Context) (bool, error) { + if ctx.existsBuffer() { + return false, nil + } + + if err := s.scanMultiLineHeaderOption(ctx); err != nil { + return false, err + } + s.progressLine(ctx) + return true, nil +} + +func (s *Scanner) validateMultiLineHeaderOption(opt string) error { + if len(opt) == 0 { + return nil + } + orgOpt := opt + opt = strings.TrimPrefix(opt, "-") + opt = strings.TrimPrefix(opt, "+") + opt = strings.TrimSuffix(opt, "-") + opt = strings.TrimSuffix(opt, "+") + if len(opt) == 0 { + return nil + } + if opt == "0" { + return fmt.Errorf("invalid header option: %q", orgOpt) + } + i, err := strconv.ParseInt(opt, 10, 64) + if err != nil { + return fmt.Errorf("invalid header option: %q", orgOpt) + } + if i > 9 { + return fmt.Errorf("invalid header option: %q", orgOpt) + } + return nil +} + +func (s *Scanner) scanMultiLineHeaderOption(ctx *Context) error { + header := ctx.currentChar() + ctx.addOriginBuf(header) + s.progress(ctx, 1) // skip '|' or '>' character + + var progress int + for idx, c := range ctx.src[ctx.idx:] { + progress = idx + ctx.addOriginBuf(c) + if s.isNewLineChar(c) { + break + } + } + value := strings.TrimRight(ctx.source(ctx.idx, ctx.idx+progress), " ") + commentValueIndex := strings.Index(value, "#") + opt := value + if commentValueIndex > 0 { + opt = value[:commentValueIndex] + } + opt = strings.TrimRightFunc(opt, func(r rune) bool { + return r == ' ' || r == '\t' + }) + if len(opt) != 0 { + if err := s.validateMultiLineHeaderOption(opt); err != nil { + invalidTk := token.Invalid(err.Error(), string(ctx.obuf), s.pos()) + s.progressColumn(ctx, progress) + return ErrInvalidToken(invalidTk) + } + } + if s.column == 1 { + s.lastDelimColumn = 1 + } + + commentIndex := strings.Index(string(ctx.obuf), "#") + headerBuf := string(ctx.obuf) + if commentIndex > 0 { + headerBuf = headerBuf[:commentIndex] + } + switch header { + case '|': + ctx.addToken(token.Literal("|"+opt, headerBuf, s.pos())) + ctx.setLiteral(s.lastDelimColumn, opt) + case '>': + ctx.addToken(token.Folded(">"+opt, headerBuf, s.pos())) + ctx.setFolded(s.lastDelimColumn, opt) + } + if commentIndex > 0 { + comment := string(value[commentValueIndex+1:]) + s.offset += len(headerBuf) + s.column += len(headerBuf) + ctx.addToken(token.Comment(comment, string(ctx.obuf[len(headerBuf):]), s.pos())) + } + s.indentState = IndentStateKeep + ctx.resetBuffer() + s.progressColumn(ctx, progress) + return nil +} + +func (s *Scanner) scanMapKey(ctx *Context) bool { + if ctx.existsBuffer() { + return false + } + + nc := ctx.nextChar() + if nc != ' ' && nc != '\t' { + return false + } + + tk := token.MappingKey(s.pos()) + s.lastDelimColumn = tk.Position.Column + ctx.addToken(tk) + s.progressColumn(ctx, 1) + ctx.clear() + return true +} + +func (s *Scanner) scanDirective(ctx *Context) bool { + if ctx.existsBuffer() { + return false + } + if s.indentNum != 0 { + return false + } + + s.addBufferedTokenIfExists(ctx) + ctx.addOriginBuf('%') + ctx.addToken(token.Directive(string(ctx.obuf), s.pos())) + s.progressColumn(ctx, 1) + ctx.clear() + s.isDirective = true + return true +} + +func (s *Scanner) scanAnchor(ctx *Context) bool { + if ctx.existsBuffer() { + return false + } + + s.addBufferedTokenIfExists(ctx) + ctx.addOriginBuf('&') + ctx.addToken(token.Anchor(string(ctx.obuf), s.pos())) + s.progressColumn(ctx, 1) + s.isAnchor = true + ctx.clear() + return true +} + +func (s *Scanner) scanAlias(ctx *Context) bool { + if ctx.existsBuffer() { + return false + } + + s.addBufferedTokenIfExists(ctx) + ctx.addOriginBuf('*') + ctx.addToken(token.Alias(string(ctx.obuf), s.pos())) + s.progressColumn(ctx, 1) + s.isAlias = true + ctx.clear() + return true +} + +func (s *Scanner) scanReservedChar(ctx *Context, c rune) error { + if ctx.existsBuffer() { + return nil + } + + ctx.addBuf(c) + ctx.addOriginBuf(c) + err := ErrInvalidToken( + token.Invalid( + fmt.Sprintf("%q is a reserved character", c), + string(ctx.obuf), s.pos(), + ), + ) + s.progressColumn(ctx, 1) + ctx.clear() + return err +} + +func (s *Scanner) scanTab(ctx *Context, c rune) error { + if s.startedFlowSequenceNum > 0 || s.startedFlowMapNum > 0 { + // tabs character is allowed in flow mode. + return nil + } + + if !s.isFirstCharAtLine { + return nil + } + + ctx.addBuf(c) + ctx.addOriginBuf(c) + err := ErrInvalidToken( + token.Invalid("found character '\t' that cannot start any token", + string(ctx.obuf), s.pos(), + ), + ) + s.progressColumn(ctx, 1) + ctx.clear() + return err +} + +func (s *Scanner) scan(ctx *Context) error { for ctx.next() { - pos = ctx.nextPos() c := ctx.currentChar() + // First, change the IndentState. + // If the target character is the first character in a line, IndentState is Up/Down/Equal state. + // The second and subsequent letters are Keep. s.updateIndent(ctx, c) - if ctx.isDocument() { - if s.isChangedToIndentStateEqual() || - s.isChangedToIndentStateDown() { - s.addBufferedTokenIfExists(ctx) - s.breakLiteral(ctx) - } else { - s.scanLiteral(ctx, c) - continue - } - } else if s.isChangedToIndentStateDown() { + + // If IndentState is down, tokens are split, so the buffer accumulated until that point needs to be cutted as a token. + if s.isChangedToIndentStateDown() { s.addBufferedTokenIfExists(ctx) - } else if s.isChangedToIndentStateEqual() { - // if first character is new line character, buffer expect to raw folded literal - if len(ctx.obuf) > 0 && s.newLineCount(ctx.obuf) <= 1 { - // doesn't raw folded literal - s.addBufferedTokenIfExists(ctx) + } + if ctx.isMultiLine() { + if s.isChangedToIndentStateDown() { + if tk := ctx.lastToken(); tk != nil { + // If literal/folded content is empty, no string token is added. + // Therefore, add an empty string token. + // But if literal/folded token column is 1, it is invalid at down state. + if tk.Position.Column == 1 { + return ErrInvalidToken( + token.Invalid( + "could not find multi-line content", + string(ctx.obuf), s.pos(), + ), + ) + } + if tk.Type != token.StringType { + ctx.addToken(token.String("", "", s.pos())) + } + } + s.breakMultiLine(ctx) + } else { + if err := s.scanMultiLine(ctx, c); err != nil { + return err + } + continue } } switch c { case '{': - if !ctx.existsBuffer() { - ctx.addOriginBuf(c) - ctx.addToken(token.MappingStart(string(ctx.obuf), s.pos())) - s.startedFlowMapNum++ - s.progressColumn(ctx, 1) - return + if s.scanFlowMapStart(ctx) { + continue } case '}': - if !ctx.existsBuffer() || s.startedFlowMapNum > 0 { - ctx.addToken(s.bufferedToken(ctx)) - ctx.addOriginBuf(c) - ctx.addToken(token.MappingEnd(string(ctx.obuf), s.pos())) - s.startedFlowMapNum-- - s.progressColumn(ctx, 1) - return + if s.scanFlowMapEnd(ctx) { + continue } case '.': - if s.indentNum == 0 && s.column == 1 && ctx.repeatNum('.') == 3 { - ctx.addToken(token.DocumentEnd(string(ctx.obuf)+"...", s.pos())) - s.progressColumn(ctx, 3) - pos += 2 - return + if s.scanDocumentEnd(ctx) { + continue } case '<': - if s.isMergeKey(ctx) { - s.prevIndentColumn = s.column - ctx.addToken(token.MergeKey(string(ctx.obuf)+"<<", s.pos())) - s.progressColumn(ctx, 1) - pos++ - return + if s.scanMergeKey(ctx) { + continue } case '-': - if s.indentNum == 0 && s.column == 1 && ctx.repeatNum('-') == 3 { - s.addBufferedTokenIfExists(ctx) - ctx.addToken(token.DocumentHeader(string(ctx.obuf)+"---", s.pos())) - s.progressColumn(ctx, 3) - pos += 2 - return - } - if ctx.existsBuffer() && s.isChangedToIndentStateUp() { - // raw folded - ctx.isRawFolded = true - ctx.addBuf(c) - ctx.addOriginBuf(c) - s.progressColumn(ctx, 1) + if s.scanDocumentStart(ctx) { continue } - if ctx.existsBuffer() { - // '-' is literal - ctx.addBuf(c) - ctx.addOriginBuf(c) - s.progressColumn(ctx, 1) + if s.scanRawFoldedChar(ctx) { continue } - nc := ctx.nextChar() - if nc == ' ' || s.isNewLineChar(nc) { - s.addBufferedTokenIfExists(ctx) - ctx.addOriginBuf(c) - tk := token.SequenceEntry(string(ctx.obuf), s.pos()) - s.prevIndentColumn = tk.Position.Column - ctx.addToken(tk) - s.progressColumn(ctx, 1) - return + scanned, err := s.scanSequence(ctx) + if err != nil { + return err + } + if scanned { + continue } case '[': - if !ctx.existsBuffer() { - ctx.addOriginBuf(c) - ctx.addToken(token.SequenceStart(string(ctx.obuf), s.pos())) - s.startedFlowSequenceNum++ - s.progressColumn(ctx, 1) - return + if s.scanFlowArrayStart(ctx) { + continue } case ']': - if !ctx.existsBuffer() || s.startedFlowSequenceNum > 0 { - s.addBufferedTokenIfExists(ctx) - ctx.addOriginBuf(c) - ctx.addToken(token.SequenceEnd(string(ctx.obuf), s.pos())) - s.startedFlowSequenceNum-- - s.progressColumn(ctx, 1) - return + if s.scanFlowArrayEnd(ctx) { + continue } case ',': - if s.startedFlowSequenceNum > 0 || s.startedFlowMapNum > 0 { - s.addBufferedTokenIfExists(ctx) - ctx.addOriginBuf(c) - ctx.addToken(token.CollectEntry(string(ctx.obuf), s.pos())) - s.progressColumn(ctx, 1) - return + if s.scanFlowEntry(ctx, c) { + continue } case ':': - nc := ctx.nextChar() - if s.startedFlowMapNum > 0 || nc == ' ' || s.isNewLineChar(nc) || ctx.isNextEOS() { - // mapping value - tk := s.bufferedToken(ctx) - if tk != nil { - s.prevIndentColumn = 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 - } - } - ctx.addToken(token.MappingValue(s.pos())) - s.progressColumn(ctx, 1) - return + scanned, err := s.scanMapDelim(ctx) + if err != nil { + return err + } + if scanned { + continue } case '|', '>': - if !ctx.existsBuffer() { - progress, err := s.scanLiteralHeader(ctx) - if err != nil { - // TODO: returns syntax error object - return - } - s.progressColumn(ctx, progress) - s.progressLine(ctx) + scanned, err := s.scanMultiLineHeader(ctx) + if err != nil { + return err + } + if scanned { continue } case '!': - if !ctx.existsBuffer() { - token, progress := s.scanTag(ctx) - ctx.addToken(token) - s.progressColumn(ctx, progress) - if c := ctx.previousChar(); s.isNewLineChar(c) { - s.progressLine(ctx) - } - pos += progress - return + scanned, err := s.scanTag(ctx) + if err != nil { + return err + } + if scanned { + continue } case '%': - if !ctx.existsBuffer() && s.indentNum == 0 { - ctx.addToken(token.Directive(string(ctx.obuf)+"%", s.pos())) - s.progressColumn(ctx, 1) - return + if s.scanDirective(ctx) { + continue } case '?': - nc := ctx.nextChar() - if !ctx.existsBuffer() && nc == ' ' { - ctx.addToken(token.MappingKey(s.pos())) - s.progressColumn(ctx, 1) - return + if s.scanMapKey(ctx) { + continue } case '&': - if !ctx.existsBuffer() { - s.addBufferedTokenIfExists(ctx) - ctx.addOriginBuf(c) - ctx.addToken(token.Anchor(string(ctx.obuf), s.pos())) - s.progressColumn(ctx, 1) - s.isAnchor = true - return + if s.scanAnchor(ctx) { + continue } case '*': - if !ctx.existsBuffer() { - s.addBufferedTokenIfExists(ctx) - ctx.addOriginBuf(c) - ctx.addToken(token.Alias(string(ctx.obuf), s.pos())) - s.progressColumn(ctx, 1) - return + if s.scanAlias(ctx) { + continue } case '#': - if !ctx.existsBuffer() || ctx.previousChar() == ' ' { - s.addBufferedTokenIfExists(ctx) - token, progress := s.scanComment(ctx) - ctx.addToken(token) - s.progressColumn(ctx, progress) - s.progressLine(ctx) - pos += progress - return + if s.scanComment(ctx) { + continue } case '\'', '"': - if !ctx.existsBuffer() { - token, progress := s.scanQuote(ctx, c) - ctx.addToken(token) - pos += progress - // If the non-whitespace character immediately following the quote is ':', the quote should be treated as a map key. - // Therefore, do not return and continue processing as a normal map key. - if ctx.currentCharWithSkipWhitespace() == ':' { - continue - } - return + scanned, err := s.scanQuote(ctx, c) + if err != nil { + return err + } + if scanned { + continue } case '\r', '\n': s.scanNewLine(ctx, c) continue case ' ': - if ctx.isSaveIndentMode() || (!s.isAnchor && !s.isFirstCharAtLine) { - ctx.addBuf(c) - ctx.addOriginBuf(c) - s.progressColumn(ctx, 1) + if s.scanWhiteSpace(ctx) { continue } - if s.isFirstCharAtLine { - s.progressColumn(ctx, 1) + case '@', '`': + if err := s.scanReservedChar(ctx, c); err != nil { + return err + } + case '\t': + if ctx.existsBuffer() && s.lastDelimColumn == 0 { + // tab indent for plain text (yaml-test-suite's spec-example-7-12-plain-lines). + s.indentNum++ ctx.addOriginBuf(c) + s.progressOnly(ctx, 1) continue } - s.addBufferedTokenIfExists(ctx) - pos-- // to rescan white space at next scanning for adding white space to next buffer. - s.isAnchor = false - return + if s.lastDelimColumn < s.column { + s.indentNum++ + ctx.addOriginBuf(c) + s.progressOnly(ctx, 1) + continue + } + if err := s.scanTab(ctx, c); err != nil { + return err + } } ctx.addBuf(c) ctx.addOriginBuf(c) s.progressColumn(ctx, 1) } s.addBufferedTokenIfExists(ctx) - return + return nil } // Init prepares the scanner s to tokenize the text src by setting the scanner at the beginning of src. @@ -880,12 +1482,15 @@ 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.isFirstCharAtLine = true + s.clearState() +} + +func (s *Scanner) clearState() { + s.prevLineIndentNum = 0 + s.lastDelimColumn = 0 s.indentLevel = 0 s.indentNum = 0 - s.isFirstCharAtLine = true } // Scan scans the next token and returns the token collection. The source end is indicated by io.EOF. @@ -895,9 +1500,17 @@ func (s *Scanner) Scan() (token.Tokens, error) { } ctx := newContext(s.source[s.sourcePos:]) defer ctx.release() - progress := s.scan(ctx) - s.sourcePos += progress + var tokens token.Tokens + err := s.scan(ctx) tokens = append(tokens, ctx.tokens...) + + if err != nil { + var invalidTokenErr *InvalidTokenError + if errors.As(err, &invalidTokenErr) { + tokens = append(tokens, invalidTokenErr.Token) + } + return tokens, err + } return tokens, nil } diff --git a/vendor/github.com/goccy/go-yaml/stdlib_quote.go b/vendor/github.com/goccy/go-yaml/stdlib_quote.go index be50ae612..120448d33 100644 --- a/vendor/github.com/goccy/go-yaml/stdlib_quote.go +++ b/vendor/github.com/goccy/go-yaml/stdlib_quote.go @@ -53,8 +53,18 @@ func appendQuotedWith(buf []byte, s string, quote byte) []byte { func appendEscapedRune(buf []byte, r rune, quote byte) []byte { var runeTmp [utf8.UTFMax]byte - if r == rune(quote) || r == '\\' { // always backslashed - buf = append(buf, '\\') + // goccy/go-yaml patch on top of the standard library's appendEscapedRune function. + // + // We use this to implement the YAML single-quoted string, where the only escape sequence is '', which represents a single quote. + // The below snippet from the standard library is for escaping e.g. \ with \\, which is not what we want for the single-quoted string. + // + // if r == rune(quote) || r == '\\' { // always backslashed + // buf = append(buf, '\\') + // buf = append(buf, byte(r)) + // return buf + // } + if r == rune(quote) { + buf = append(buf, byte(r)) buf = append(buf, byte(r)) return buf } diff --git a/vendor/github.com/goccy/go-yaml/struct.go b/vendor/github.com/goccy/go-yaml/struct.go index a3da8ddd3..ece77f563 100644 --- a/vendor/github.com/goccy/go-yaml/struct.go +++ b/vendor/github.com/goccy/go-yaml/struct.go @@ -1,10 +1,9 @@ package yaml import ( + "fmt" "reflect" "strings" - - "golang.org/x/xerrors" ) const ( @@ -21,6 +20,7 @@ type StructField struct { IsAutoAnchor bool IsAutoAlias bool IsOmitEmpty bool + IsOmitZero bool IsFlow bool IsInline bool } @@ -45,7 +45,7 @@ func structField(field reflect.StructField) *StructField { fieldName = options[0] } } - structField := &StructField{ + sf := &StructField{ FieldName: field.Name, RenderName: fieldName, } @@ -53,30 +53,32 @@ func structField(field reflect.StructField) *StructField { for _, opt := range options[1:] { switch { case opt == "omitempty": - structField.IsOmitEmpty = true + sf.IsOmitEmpty = true + case opt == "omitzero": + sf.IsOmitZero = true case opt == "flow": - structField.IsFlow = true + sf.IsFlow = true case opt == "inline": - structField.IsInline = true + sf.IsInline = true case strings.HasPrefix(opt, "anchor"): anchor := strings.Split(opt, "=") if len(anchor) > 1 { - structField.AnchorName = anchor[1] + sf.AnchorName = anchor[1] } else { - structField.IsAutoAnchor = true + sf.IsAutoAnchor = true } case strings.HasPrefix(opt, "alias"): alias := strings.Split(opt, "=") if len(alias) > 1 { - structField.AliasName = alias[1] + sf.AliasName = alias[1] } else { - structField.IsAutoAlias = true + sf.IsAutoAlias = true } default: } } } - return structField + return sf } func isIgnoredStructField(field reflect.StructField) bool { @@ -84,11 +86,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 @@ -112,19 +110,19 @@ func (m StructFieldMap) hasMergeProperty() bool { } func structFieldMap(structType reflect.Type) (StructFieldMap, error) { - structFieldMap := StructFieldMap{} + fieldMap := StructFieldMap{} renderNameMap := map[string]struct{}{} for i := 0; i < structType.NumField(); i++ { field := structType.Field(i) if isIgnoredStructField(field) { continue } - structField := structField(field) - if _, exists := renderNameMap[structField.RenderName]; exists { - return nil, xerrors.Errorf("duplicated struct field name %s", structField.RenderName) + sf := structField(field) + if _, exists := renderNameMap[sf.RenderName]; exists { + return nil, fmt.Errorf("duplicated struct field name %s", sf.RenderName) } - structFieldMap[structField.FieldName] = structField - renderNameMap[structField.RenderName] = struct{}{} + fieldMap[sf.FieldName] = sf + renderNameMap[sf.RenderName] = struct{}{} } - return structFieldMap, nil + return fieldMap, nil } diff --git a/vendor/github.com/goccy/go-yaml/token/token.go b/vendor/github.com/goccy/go-yaml/token/token.go index 14d76220b..e88735624 100644 --- a/vendor/github.com/goccy/go-yaml/token/token.go +++ b/vendor/github.com/goccy/go-yaml/token/token.go @@ -1,8 +1,11 @@ package token import ( + "errors" "fmt" + "strconv" "strings" + "time" ) // Character type for character @@ -99,6 +102,10 @@ const ( SpaceType // NullType type for Null token NullType + // ImplicitNullType type for implicit Null token. + // This is used when explicit keywords such as null or ~ are not specified. + // It is distinguished during encoding and output as an empty string. + ImplicitNullType // InfinityType type for Infinity token InfinityType // NanType type for Nan token @@ -117,6 +124,8 @@ const ( StringType // BoolType type for Bool token BoolType + // InvalidType type for invalid token + InvalidType ) // String type identifier to text @@ -182,10 +191,14 @@ func (t Type) String() string { return "Float" case NullType: return "Null" + case ImplicitNullType: + return "ImplicitNull" case InfinityType: return "Infinity" case NanType: return "Nan" + case InvalidType: + return "Invalid" } return "" } @@ -202,6 +215,8 @@ const ( CharacterTypeMiscellaneous // CharacterTypeEscaped type of escaped character CharacterTypeEscaped + // CharacterTypeInvalid type for a invalid token. + CharacterTypeInvalid ) // String character type identifier to text @@ -210,7 +225,7 @@ func (c CharacterType) String() string { case CharacterTypeIndicator: return "Indicator" case CharacterTypeWhiteSpace: - return "WhiteSpcae" + return "WhiteSpace" case CharacterTypeMiscellaneous: return "Miscellaneous" case CharacterTypeEscaped: @@ -339,9 +354,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 { @@ -391,6 +409,10 @@ const ( SetTag ReservedTagKeyword = "!!set" // TimestampTag `!!timestamp` tag TimestampTag ReservedTagKeyword = "!!timestamp" + // BooleanTag `!!bool` tag + BooleanTag ReservedTagKeyword = "!!bool" + // MergeTag `!!merge` tag + MergeTag ReservedTagKeyword = "!!merge" ) var ( @@ -496,121 +518,163 @@ var ( Position: pos, } }, + BooleanTag: func(value, org string, pos *Position) *Token { + return &Token{ + Type: TagType, + CharacterType: CharacterTypeIndicator, + Indicator: NodePropertyIndicator, + Value: value, + Origin: org, + Position: pos, + } + }, + MergeTag: func(value, org string, pos *Position) *Token { + return &Token{ + Type: TagType, + CharacterType: CharacterTypeIndicator, + Indicator: NodePropertyIndicator, + Value: value, + Origin: org, + Position: pos, + } + }, } ) -type numType int +type NumberType string const ( - numTypeNone numType = iota - numTypeBinary - numTypeOctet - numTypeHex - numTypeFloat + NumberTypeDecimal NumberType = "decimal" + NumberTypeBinary NumberType = "binary" + NumberTypeOctet NumberType = "octet" + NumberTypeHex NumberType = "hex" + NumberTypeFloat NumberType = "float" ) -type numStat struct { - isNum bool - typ numType +type NumberValue struct { + Type NumberType + Value any + Text string } -func getNumberStat(str string) *numStat { - stat := &numStat{} - if str == "" { - return stat +func ToNumber(value string) *NumberValue { + num, err := toNumber(value) + if err != nil { + return nil } - if str == "-" || str == "." || str == "+" || str == "_" { - return stat - } - if str[0] == '_' { - return stat - } - dotFound := false - isNegative := false - isExponent := false - if str[0] == '-' { - isNegative = true - } - for idx, c := range str { - switch c { - case 'x': - if (isNegative && idx == 2) || (!isNegative && idx == 1) { - continue - } - case 'o': - if (isNegative && idx == 2) || (!isNegative && idx == 1) { - continue - } - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - continue - case 'a', 'b', 'c', 'd', 'e', 'f', 'A', 'B', 'C', 'D', 'E', 'F': - if (len(str) > 2 && str[0] == '0' && str[1] == 'x') || - (len(str) > 3 && isNegative && str[1] == '0' && str[2] == 'x') { - // hex number - continue - } - if c == 'b' && ((isNegative && idx == 2) || (!isNegative && idx == 1)) { - // binary number - continue - } - if (c == 'e' || c == 'E') && dotFound { - // exponent - isExponent = true - continue - } - case '.': - if dotFound { - // multiple dot - return stat - } - dotFound = true - continue - case '-': - if idx == 0 || isExponent { - continue - } - case '+': - if idx == 0 || isExponent { - continue - } - case '_': - continue - } - return stat - } - stat.isNum = true - switch { - case dotFound: - stat.typ = numTypeFloat - case strings.HasPrefix(str, "0b") || strings.HasPrefix(str, "-0b"): - stat.typ = numTypeBinary - case strings.HasPrefix(str, "0x") || strings.HasPrefix(str, "-0x"): - stat.typ = numTypeHex - case strings.HasPrefix(str, "0o") || strings.HasPrefix(str, "-0o"): - stat.typ = numTypeOctet - case (len(str) > 1 && str[0] == '0') || (len(str) > 1 && str[0] == '-' && str[1] == '0'): - stat.typ = numTypeOctet - } - return stat + return num } -func looksLikeTimeValue(value string) bool { - for i, c := range value { - switch c { - case ':', '1', '2', '3', '4', '5', '6', '7', '8', '9': - continue - case '0': - if i == 0 { - return false - } - continue +func isNumber(value string) bool { + num, err := toNumber(value) + if err != nil { + var numErr *strconv.NumError + if errors.As(err, &numErr) && errors.Is(numErr.Err, strconv.ErrRange) { + return true } return false } - return true + return num != nil } -// IsNeedQuoted whether need quote for passed string or not +func toNumber(value string) (*NumberValue, error) { + if len(value) == 0 { + return nil, nil + } + if strings.HasPrefix(value, "_") { + return nil, nil + } + dotCount := strings.Count(value, ".") + if dotCount > 1 { + return nil, nil + } + + isNegative := strings.HasPrefix(value, "-") + normalized := strings.ReplaceAll(strings.TrimPrefix(strings.TrimPrefix(value, "+"), "-"), "_", "") + + var ( + typ NumberType + base int + ) + switch { + case strings.HasPrefix(normalized, "0x"): + normalized = strings.TrimPrefix(normalized, "0x") + base = 16 + typ = NumberTypeHex + case strings.HasPrefix(normalized, "0o"): + normalized = strings.TrimPrefix(normalized, "0o") + base = 8 + typ = NumberTypeOctet + case strings.HasPrefix(normalized, "0b"): + normalized = strings.TrimPrefix(normalized, "0b") + base = 2 + typ = NumberTypeBinary + case strings.HasPrefix(normalized, "0") && len(normalized) > 1 && dotCount == 0: + base = 8 + typ = NumberTypeOctet + case dotCount == 1: + typ = NumberTypeFloat + default: + typ = NumberTypeDecimal + base = 10 + } + + text := normalized + if isNegative { + text = "-" + text + } + + var v any + if typ == NumberTypeFloat { + f, err := strconv.ParseFloat(text, 64) + if err != nil { + return nil, err + } + v = f + } else if isNegative { + i, err := strconv.ParseInt(text, base, 64) + if err != nil { + return nil, err + } + v = i + } else { + u, err := strconv.ParseUint(text, base, 64) + if err != nil { + return nil, err + } + v = u + } + + return &NumberValue{ + Type: typ, + Value: v, + Text: text, + }, nil +} + +// This is a subset of the formats permitted by the regular expression +// defined at http://yaml.org/type/timestamp.html. Note that time.Parse +// cannot handle: "2001-12-14 21:59:43.10 -5" from the examples. +var timestampFormats = []string{ + time.RFC3339Nano, + "2006-01-02t15:04:05.999999999Z07:00", // RFC3339Nano with lower-case "t". + time.DateTime, + time.DateOnly, + + // Not in examples, but to preserve backward compatibility by quoting time values. + "15:4", +} + +func isTimestamp(value string) bool { + for _, format := range timestampFormats { + if _, err := time.Parse(format, value); err == nil { + return true + } + } + return false +} + +// IsNeedQuoted checks whether the value needs quote for passed string or not func IsNeedQuoted(value string) bool { if value == "" { return true @@ -618,7 +682,10 @@ func IsNeedQuoted(value string) bool { if _, exists := reservedEncKeywordMap[value]; exists { return true } - if stat := getNumberStat(value); stat.isNum { + if isNumber(value) { + return true + } + if value == "-" { return true } first := value[0] @@ -631,14 +698,14 @@ func IsNeedQuoted(value string) bool { case ':', ' ': return true } - if looksLikeTimeValue(value) { + if isTimestamp(value) { return true } for i, c := range value { switch c { case '#', '\\': return true - case ':': + case ':', '-': if i+1 < len(value) && value[i+1] == ' ' { return true } @@ -663,13 +730,13 @@ func LiteralBlockHeader(value string) string { } } -// New create reserved keyword token or number token and other string token +// New create reserved keyword token or number token and other string token. func New(value string, org string, pos *Position) *Token { fn := reservedKeywordMap[value] if fn != nil { return fn(value, org, pos) } - if stat := getNumberStat(value); stat.isNum { + if num := ToNumber(value); num != nil { tk := &Token{ Type: IntegerType, CharacterType: CharacterTypeMiscellaneous, @@ -678,14 +745,14 @@ func New(value string, org string, pos *Position) *Token { Origin: org, Position: pos, } - switch stat.typ { - case numTypeFloat: + switch num.Type { + case NumberTypeFloat: tk.Type = FloatType - case numTypeBinary: + case NumberTypeBinary: tk.Type = BinaryIntegerType - case numTypeOctet: + case NumberTypeOctet: tk.Type = OctetIntegerType - case numTypeHex: + case NumberTypeHex: tk.Type = HexIntegerType } return tk @@ -709,14 +776,24 @@ func (p *Position) String() string { // Token type for token type Token struct { - Type Type + // Type is a token type. + Type Type + // CharacterType is a character type. CharacterType CharacterType - Indicator Indicator - Value string - Origin string - Position *Position - Next *Token - Prev *Token + // Indicator is a indicator type. + Indicator Indicator + // Value is a string extracted with only meaningful characters, with spaces and such removed. + Value string + // Origin is a string that stores the original text as-is. + Origin string + // Error keeps error message for InvalidToken. + Error string + // Position is a token position. + Position *Position + // Next is a next token reference. + Next *Token + // Prev is a previous token reference. + Prev *Token } // PreviousType previous token type @@ -756,9 +833,26 @@ func (t *Token) Clone() *Token { return &copied } +// Dump outputs token information to stdout for debugging. +func (t *Token) Dump() { + fmt.Printf( + "[TYPE]:%q [CHARTYPE]:%q [INDICATOR]:%q [VALUE]:%q [ORG]:%q [POS(line:column:level:offset)]: %d:%d:%d:%d\n", + t.Type, t.CharacterType, t.Indicator, t.Value, t.Origin, t.Position.Line, t.Position.Column, t.Position.IndentLevel, t.Position.Offset, + ) +} + // Tokens type of token collection type Tokens []*Token +func (t Tokens) InvalidToken() *Token { + for _, tt := range t { + if tt.Type == InvalidType { + return tt + } + } + return nil +} + func (t *Tokens) add(tk *Token) { tokens := *t if len(tokens) == 0 { @@ -782,7 +876,8 @@ func (t *Tokens) Add(tks ...*Token) { // Dump dump all token structures for debugging func (t Tokens) Dump() { for _, tk := range t { - fmt.Printf("- %+v\n", tk) + fmt.Print("- ") + tk.Dump() } } @@ -1054,6 +1149,18 @@ func DocumentEnd(org string, pos *Position) *Token { } } +func Invalid(err string, org string, pos *Position) *Token { + return &Token{ + Type: InvalidType, + CharacterType: CharacterTypeInvalid, + Indicator: NotIndicator, + Value: org, + Origin: org, + Error: err, + Position: pos, + } +} + // DetectLineBreakCharacter detect line break character in only one inside scalar content scope. func DetectLineBreakCharacter(src string) string { nc := strings.Count(src, "\n") diff --git a/vendor/github.com/goccy/go-yaml/yaml.go b/vendor/github.com/goccy/go-yaml/yaml.go index 25b1056fe..f9a9f83bf 100644 --- a/vendor/github.com/goccy/go-yaml/yaml.go +++ b/vendor/github.com/goccy/go-yaml/yaml.go @@ -9,7 +9,6 @@ import ( "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 @@ -58,6 +57,16 @@ type InterfaceUnmarshalerContext interface { UnmarshalYAML(context.Context, func(interface{}) error) error } +// NodeUnmarshaler interface is similar to BytesUnmarshaler but provide related AST node instead of raw YAML source. +type NodeUnmarshaler interface { + UnmarshalYAML(ast.Node) error +} + +// NodeUnmarshalerContext interface is similar to BytesUnmarshaler but provide related AST node instead of raw YAML source. +type NodeUnmarshalerContext interface { + UnmarshalYAML(context.Context, ast.Node) error +} + // MapItem is an item in a MapSlice. type MapItem struct { Key, Value interface{} @@ -80,11 +89,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: @@ -99,6 +108,13 @@ func (s MapSlice) ToMap() map[interface{}]interface{} { // fields are zero, unless they implement an IsZero // method (see the IsZeroer interface type), in which // case the field will be included if that method returns true. +// Note that this definition is slightly different from the Go's +// encoding/json 'omitempty' definition. It combines some elements +// of 'omitempty' and 'omitzero'. See https://github.com/goccy/go-yaml/issues/695. +// +// omitzero The omitzero tag behaves in the same way as the interpretation of the omitzero tag in the encoding/json library. +// 1) If the field type has an "IsZero() bool" method, that will be used to determine whether the value is zero. +// 2) Otherwise, the value is zero if it is the zero value for its type. // // flow Marshal using a flow style (useful for structs, // sequences and maps). @@ -138,7 +154,7 @@ func MarshalWithOptions(v interface{}, opts ...EncodeOption) ([]byte, error) { func MarshalContext(ctx context.Context, v interface{}, opts ...EncodeOption) ([]byte, error) { var buf bytes.Buffer if err := NewEncoder(&buf, opts...).EncodeContext(ctx, v); err != nil { - return nil, errors.Wrapf(err, "failed to marshal") + return nil, err } return buf.Bytes(), nil } @@ -148,7 +164,7 @@ func ValueToNode(v interface{}, opts ...EncodeOption) (ast.Node, error) { var buf bytes.Buffer node, err := NewEncoder(&buf, opts...).EncodeToNode(v) if err != nil { - return nil, errors.Wrapf(err, "failed to convert value to node") + return nil, err } return node, nil } @@ -161,7 +177,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: @@ -192,7 +208,7 @@ func UnmarshalContext(ctx context.Context, data []byte, v interface{}, opts ...D if err == io.EOF { return nil } - return errors.Wrapf(err, "failed to unmarshal") + return err } return nil } @@ -201,7 +217,7 @@ func UnmarshalContext(ctx context.Context, data []byte, v interface{}, opts ...D func NodeToValue(node ast.Node, v interface{}, opts ...DecodeOption) error { var buf bytes.Buffer if err := NewDecoder(&buf, opts...).DecodeFromNode(node, v); err != nil { - return errors.Wrapf(err, "failed to convert node to value") + return err } return nil } @@ -213,11 +229,9 @@ func NodeToValue(node ast.Node, v interface{}, opts ...DecodeOption) error { // If the third argument `inclSource` is true, the error message will // contain snippets of the YAML source that was used. 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) - return buf.String() + var yamlErr Error + if errors.As(e, &yamlErr) { + return yamlErr.FormatError(colored, inclSource) } return e.Error() @@ -227,11 +241,11 @@ func FormatError(e error, colored, inclSource bool) string { func YAMLToJSON(bytes []byte) ([]byte, error) { var v interface{} if err := UnmarshalWithOptions(bytes, &v, UseOrderedMap()); err != nil { - return nil, errors.Wrapf(err, "failed to unmarshal") + return nil, err } out, err := MarshalWithOptions(v, JSON()) if err != nil { - return nil, errors.Wrapf(err, "failed to marshal with json option") + return nil, err } return out, nil } @@ -240,11 +254,11 @@ func YAMLToJSON(bytes []byte) ([]byte, error) { func JSONToYAML(bytes []byte) ([]byte, error) { var v interface{} if err := UnmarshalWithOptions(bytes, &v, UseOrderedMap()); err != nil { - return nil, errors.Wrapf(err, "failed to unmarshal from json bytes") + return nil, err } out, err := Marshal(v) if err != nil { - return nil, errors.Wrapf(err, "failed to marshal") + return nil, err } return out, nil } @@ -252,8 +266,8 @@ func JSONToYAML(bytes []byte) ([]byte, error) { var ( globalCustomMarshalerMu sync.Mutex globalCustomUnmarshalerMu sync.Mutex - globalCustomMarshalerMap = map[reflect.Type]func(interface{}) ([]byte, error){} - globalCustomUnmarshalerMap = map[reflect.Type]func(interface{}, []byte) error{} + globalCustomMarshalerMap = map[reflect.Type]func(context.Context, interface{}) ([]byte, error){} + globalCustomUnmarshalerMap = map[reflect.Type]func(context.Context, interface{}, []byte) error{} ) // RegisterCustomMarshaler overrides any encoding process for the type specified in generics. @@ -267,11 +281,23 @@ func RegisterCustomMarshaler[T any](marshaler func(T) ([]byte, error)) { defer globalCustomMarshalerMu.Unlock() var typ T - globalCustomMarshalerMap[reflect.TypeOf(typ)] = func(v interface{}) ([]byte, error) { + globalCustomMarshalerMap[reflect.TypeOf(typ)] = func(ctx context.Context, v interface{}) ([]byte, error) { return marshaler(v.(T)) } } +// RegisterCustomMarshalerContext overrides any encoding process for the type specified in generics. +// Similar to RegisterCustomMarshalerContext, but allows passing a context to the unmarshaler function. +func RegisterCustomMarshalerContext[T any](marshaler func(context.Context, T) ([]byte, error)) { + globalCustomMarshalerMu.Lock() + defer globalCustomMarshalerMu.Unlock() + + var typ T + globalCustomMarshalerMap[reflect.TypeOf(typ)] = func(ctx context.Context, v interface{}) ([]byte, error) { + return marshaler(ctx, v.(T)) + } +} + // RegisterCustomUnmarshaler overrides any decoding process for the type specified in generics. // If you want to switch the behavior for each decoder, use `CustomUnmarshaler` defined as DecodeOption. // @@ -282,7 +308,19 @@ func RegisterCustomUnmarshaler[T any](unmarshaler func(*T, []byte) error) { defer globalCustomUnmarshalerMu.Unlock() var typ *T - globalCustomUnmarshalerMap[reflect.TypeOf(typ)] = func(v interface{}, b []byte) error { + globalCustomUnmarshalerMap[reflect.TypeOf(typ)] = func(ctx context.Context, v interface{}, b []byte) error { return unmarshaler(v.(*T), b) } } + +// RegisterCustomUnmarshalerContext overrides any decoding process for the type specified in generics. +// Similar to RegisterCustomUnmarshalerContext, but allows passing a context to the unmarshaler function. +func RegisterCustomUnmarshalerContext[T any](unmarshaler func(context.Context, *T, []byte) error) { + globalCustomUnmarshalerMu.Lock() + defer globalCustomUnmarshalerMu.Unlock() + + var typ *T + globalCustomUnmarshalerMap[reflect.TypeOf(typ)] = func(ctx context.Context, v interface{}, b []byte) error { + return unmarshaler(ctx, v.(*T), b) + } +} diff --git a/vendor/github.com/onsi/ginkgo/v2/CHANGELOG.md b/vendor/github.com/onsi/ginkgo/v2/CHANGELOG.md index 0e5f23782..9db388356 100644 --- a/vendor/github.com/onsi/ginkgo/v2/CHANGELOG.md +++ b/vendor/github.com/onsi/ginkgo/v2/CHANGELOG.md @@ -1,3 +1,9 @@ +## 2.26.0 + +### Features + +Ginkgo can now generate json-formatted reports that are compatible with the `go test` json format. Use `ginkgo --gojson-report=report.go.json`. This is not intended to be a replacement for Ginkgo's native json format which is more information rich and better models Ginkgo's test structure semantics. + ## 2.25.3 ### Fixes diff --git a/vendor/github.com/onsi/ginkgo/v2/README.md b/vendor/github.com/onsi/ginkgo/v2/README.md index e3d0c13cc..7b7ab9e39 100644 --- a/vendor/github.com/onsi/ginkgo/v2/README.md +++ b/vendor/github.com/onsi/ginkgo/v2/README.md @@ -113,3 +113,13 @@ Ginkgo is MIT-Licensed ## Contributing See [CONTRIBUTING.md](CONTRIBUTING.md) + +## Sponsors + +Sponsors commit to a [sponsorship](https://github.com/sponsors/onsi) for a year. If you're an organization that makes use of Ginkgo please consider becoming a sponsor! + +

Browser testing via + + + +

diff --git a/vendor/github.com/onsi/ginkgo/v2/ginkgo/internal/profiles_and_reports.go b/vendor/github.com/onsi/ginkgo/v2/ginkgo/internal/profiles_and_reports.go index 8e16d2bb0..f3439a3f0 100644 --- a/vendor/github.com/onsi/ginkgo/v2/ginkgo/internal/profiles_and_reports.go +++ b/vendor/github.com/onsi/ginkgo/v2/ginkgo/internal/profiles_and_reports.go @@ -90,6 +90,9 @@ func FinalizeProfilesAndReportsForSuites(suites TestSuites, cliConfig types.CLIC if reporterConfig.JSONReport != "" { reportFormats = append(reportFormats, reportFormat{ReportName: reporterConfig.JSONReport, GenerateFunc: reporters.GenerateJSONReport, MergeFunc: reporters.MergeAndCleanupJSONReports}) } + if reporterConfig.GoJSONReport != "" { + reportFormats = append(reportFormats, reportFormat{ReportName: reporterConfig.GoJSONReport, GenerateFunc: reporters.GenerateGoTestJSONReport, MergeFunc: reporters.MergeAndCleanupGoTestJSONReports}) + } if reporterConfig.JUnitReport != "" { reportFormats = append(reportFormats, reportFormat{ReportName: reporterConfig.JUnitReport, GenerateFunc: reporters.GenerateJUnitReport, MergeFunc: reporters.MergeAndCleanupJUnitReports}) } diff --git a/vendor/github.com/onsi/ginkgo/v2/ginkgo/internal/run.go b/vendor/github.com/onsi/ginkgo/v2/ginkgo/internal/run.go index 41052ea19..30d8096cd 100644 --- a/vendor/github.com/onsi/ginkgo/v2/ginkgo/internal/run.go +++ b/vendor/github.com/onsi/ginkgo/v2/ginkgo/internal/run.go @@ -107,6 +107,9 @@ func runSerial(suite TestSuite, ginkgoConfig types.SuiteConfig, reporterConfig t if reporterConfig.JSONReport != "" { reporterConfig.JSONReport = AbsPathForGeneratedAsset(reporterConfig.JSONReport, suite, cliConfig, 0) } + if reporterConfig.GoJSONReport != "" { + reporterConfig.GoJSONReport = AbsPathForGeneratedAsset(reporterConfig.GoJSONReport, suite, cliConfig, 0) + } if reporterConfig.JUnitReport != "" { reporterConfig.JUnitReport = AbsPathForGeneratedAsset(reporterConfig.JUnitReport, suite, cliConfig, 0) } @@ -179,6 +182,9 @@ func runParallel(suite TestSuite, ginkgoConfig types.SuiteConfig, reporterConfig if reporterConfig.JSONReport != "" { reporterConfig.JSONReport = AbsPathForGeneratedAsset(reporterConfig.JSONReport, suite, cliConfig, 0) } + if reporterConfig.GoJSONReport != "" { + reporterConfig.GoJSONReport = AbsPathForGeneratedAsset(reporterConfig.GoJSONReport, suite, cliConfig, 0) + } if reporterConfig.JUnitReport != "" { reporterConfig.JUnitReport = AbsPathForGeneratedAsset(reporterConfig.JUnitReport, suite, cliConfig, 0) } diff --git a/vendor/github.com/onsi/ginkgo/v2/ginkgo/main.go b/vendor/github.com/onsi/ginkgo/v2/ginkgo/main.go index bd6b8fbff..419589b48 100644 --- a/vendor/github.com/onsi/ginkgo/v2/ginkgo/main.go +++ b/vendor/github.com/onsi/ginkgo/v2/ginkgo/main.go @@ -3,7 +3,6 @@ package main import ( "fmt" "os" - _ "go.uber.org/automaxprocs" "github.com/onsi/ginkgo/v2/ginkgo/build" "github.com/onsi/ginkgo/v2/ginkgo/command" "github.com/onsi/ginkgo/v2/ginkgo/generators" diff --git a/vendor/github.com/onsi/ginkgo/v2/ginkgo/maxprocs.go b/vendor/github.com/onsi/ginkgo/v2/ginkgo/maxprocs.go new file mode 100644 index 000000000..a7d2b37d6 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/ginkgo/maxprocs.go @@ -0,0 +1,8 @@ +//go:build !go1.25 +// +build !go1.25 + +package main + +import ( + _ "go.uber.org/automaxprocs" +) diff --git a/vendor/github.com/onsi/ginkgo/v2/internal/reporters/gojson.go b/vendor/github.com/onsi/ginkgo/v2/internal/reporters/gojson.go new file mode 100644 index 000000000..8b7a9ceab --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/internal/reporters/gojson.go @@ -0,0 +1,158 @@ +package reporters + +import ( + "errors" + "fmt" + "strings" + "time" + + "github.com/onsi/ginkgo/v2/types" + "golang.org/x/tools/go/packages" +) + +func ptr[T any](in T) *T { + return &in +} + +type encoder interface { + Encode(v any) error +} + +// gojsonEvent matches the format from go internals +// https://github.com/golang/go/blob/master/src/cmd/internal/test2json/test2json.go#L31-L41 +// https://pkg.go.dev/cmd/test2json +type gojsonEvent struct { + Time *time.Time `json:",omitempty"` + Action GoJSONAction + Package string `json:",omitempty"` + Test string `json:",omitempty"` + Elapsed *float64 `json:",omitempty"` + Output *string `json:",omitempty"` + FailedBuild string `json:",omitempty"` +} + +type GoJSONAction string + +const ( + // start - the test binary is about to be executed + GoJSONStart GoJSONAction = "start" + // run - the test has started running + GoJSONRun GoJSONAction = "run" + // pause - the test has been paused + GoJSONPause GoJSONAction = "pause" + // cont - the test has continued running + GoJSONCont GoJSONAction = "cont" + // pass - the test passed + GoJSONPass GoJSONAction = "pass" + // bench - the benchmark printed log output but did not fail + GoJSONBench GoJSONAction = "bench" + // fail - the test or benchmark failed + GoJSONFail GoJSONAction = "fail" + // output - the test printed output + GoJSONOutput GoJSONAction = "output" + // skip - the test was skipped or the package contained no tests + GoJSONSkip GoJSONAction = "skip" +) + +func goJSONActionFromSpecState(state types.SpecState) GoJSONAction { + switch state { + case types.SpecStateInvalid: + return GoJSONFail + case types.SpecStatePending: + return GoJSONSkip + case types.SpecStateSkipped: + return GoJSONSkip + case types.SpecStatePassed: + return GoJSONPass + case types.SpecStateFailed: + return GoJSONFail + case types.SpecStateAborted: + return GoJSONFail + case types.SpecStatePanicked: + return GoJSONFail + case types.SpecStateInterrupted: + return GoJSONFail + case types.SpecStateTimedout: + return GoJSONFail + default: + panic("unexpected state should not happen") + } +} + +// gojsonReport wraps types.Report and calcualtes extra fields requires by gojson +type gojsonReport struct { + o types.Report + // Extra calculated fields + goPkg string + elapsed float64 +} + +func newReport(in types.Report) *gojsonReport { + return &gojsonReport{ + o: in, + } +} + +func (r *gojsonReport) Fill() error { + // NOTE: could the types.Report include the go package name? + goPkg, err := suitePathToPkg(r.o.SuitePath) + if err != nil { + return err + } + r.goPkg = goPkg + r.elapsed = r.o.RunTime.Seconds() + return nil +} + +// gojsonSpecReport wraps types.SpecReport and calculates extra fields required by gojson +type gojsonSpecReport struct { + o types.SpecReport + // extra calculated fields + testName string + elapsed float64 + action GoJSONAction +} + +func newSpecReport(in types.SpecReport) *gojsonSpecReport { + return &gojsonSpecReport{ + o: in, + } +} + +func (sr *gojsonSpecReport) Fill() error { + sr.elapsed = sr.o.RunTime.Seconds() + sr.testName = createTestName(sr.o) + sr.action = goJSONActionFromSpecState(sr.o.State) + return nil +} + +func suitePathToPkg(dir string) (string, error) { + cfg := &packages.Config{ + Mode: packages.NeedFiles | packages.NeedSyntax, + } + pkgs, err := packages.Load(cfg, dir) + if err != nil { + return "", err + } + if len(pkgs) != 1 { + return "", errors.New("error") + } + return pkgs[0].ID, nil +} + +func createTestName(spec types.SpecReport) string { + name := fmt.Sprintf("[%s]", spec.LeafNodeType) + if spec.FullText() != "" { + name = name + " " + spec.FullText() + } + labels := spec.Labels() + if len(labels) > 0 { + name = name + " [" + strings.Join(labels, ", ") + "]" + } + semVerConstraints := spec.SemVerConstraints() + if len(semVerConstraints) > 0 { + name = name + " [" + strings.Join(semVerConstraints, ", ") + "]" + } + name = strings.TrimSpace(name) + return name +} diff --git a/vendor/github.com/onsi/ginkgo/v2/internal/reporters/gojson_event_writer.go b/vendor/github.com/onsi/ginkgo/v2/internal/reporters/gojson_event_writer.go new file mode 100644 index 000000000..ec5311d06 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/internal/reporters/gojson_event_writer.go @@ -0,0 +1,111 @@ +package reporters + +type GoJSONEventWriter struct { + enc encoder + specSystemErrFn specSystemExtractFn + specSystemOutFn specSystemExtractFn +} + +func NewGoJSONEventWriter(enc encoder, errFn specSystemExtractFn, outFn specSystemExtractFn) *GoJSONEventWriter { + return &GoJSONEventWriter{ + enc: enc, + specSystemErrFn: errFn, + specSystemOutFn: outFn, + } +} + +func (r *GoJSONEventWriter) writeEvent(e *gojsonEvent) error { + return r.enc.Encode(e) +} + +func (r *GoJSONEventWriter) WriteSuiteStart(report *gojsonReport) error { + e := &gojsonEvent{ + Time: &report.o.StartTime, + Action: GoJSONStart, + Package: report.goPkg, + Output: nil, + FailedBuild: "", + } + return r.writeEvent(e) +} + +func (r *GoJSONEventWriter) WriteSuiteResult(report *gojsonReport) error { + var action GoJSONAction + switch { + case report.o.PreRunStats.SpecsThatWillRun == 0: + action = GoJSONSkip + case report.o.SuiteSucceeded: + action = GoJSONPass + default: + action = GoJSONFail + } + e := &gojsonEvent{ + Time: &report.o.EndTime, + Action: action, + Package: report.goPkg, + Output: nil, + FailedBuild: "", + Elapsed: ptr(report.elapsed), + } + return r.writeEvent(e) +} + +func (r *GoJSONEventWriter) WriteSpecStart(report *gojsonReport, specReport *gojsonSpecReport) error { + e := &gojsonEvent{ + Time: &specReport.o.StartTime, + Action: GoJSONRun, + Test: specReport.testName, + Package: report.goPkg, + Output: nil, + FailedBuild: "", + } + return r.writeEvent(e) +} + +func (r *GoJSONEventWriter) WriteSpecOut(report *gojsonReport, specReport *gojsonSpecReport) error { + events := []*gojsonEvent{} + + stdErr := r.specSystemErrFn(specReport.o) + if stdErr != "" { + events = append(events, &gojsonEvent{ + Time: &specReport.o.EndTime, + Action: GoJSONOutput, + Test: specReport.testName, + Package: report.goPkg, + Output: ptr(stdErr), + FailedBuild: "", + }) + } + stdOut := r.specSystemOutFn(specReport.o) + if stdOut != "" { + events = append(events, &gojsonEvent{ + Time: &specReport.o.EndTime, + Action: GoJSONOutput, + Test: specReport.testName, + Package: report.goPkg, + Output: ptr(stdOut), + FailedBuild: "", + }) + } + + for _, ev := range events { + err := r.writeEvent(ev) + if err != nil { + return err + } + } + return nil +} + +func (r *GoJSONEventWriter) WriteSpecResult(report *gojsonReport, specReport *gojsonSpecReport) error { + e := &gojsonEvent{ + Time: &specReport.o.EndTime, + Action: specReport.action, + Test: specReport.testName, + Package: report.goPkg, + Elapsed: ptr(specReport.elapsed), + Output: nil, + FailedBuild: "", + } + return r.writeEvent(e) +} diff --git a/vendor/github.com/onsi/ginkgo/v2/internal/reporters/gojson_reporter.go b/vendor/github.com/onsi/ginkgo/v2/internal/reporters/gojson_reporter.go new file mode 100644 index 000000000..633e49b88 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/internal/reporters/gojson_reporter.go @@ -0,0 +1,45 @@ +package reporters + +import ( + "github.com/onsi/ginkgo/v2/types" +) + +type GoJSONReporter struct { + ev *GoJSONEventWriter +} + +type specSystemExtractFn func (spec types.SpecReport) string + +func NewGoJSONReporter(enc encoder, errFn specSystemExtractFn, outFn specSystemExtractFn) *GoJSONReporter { + return &GoJSONReporter{ + ev: NewGoJSONEventWriter(enc, errFn, outFn), + } +} + +func (r *GoJSONReporter) Write(originalReport types.Report) error { + // suite start events + report := newReport(originalReport) + err := report.Fill() + if err != nil { + return err + } + r.ev.WriteSuiteStart(report) + for _, originalSpecReport := range originalReport.SpecReports { + specReport := newSpecReport(originalSpecReport) + err := specReport.Fill() + if err != nil { + return err + } + if specReport.o.LeafNodeType == types.NodeTypeIt { + // handle any It leaf node as a spec + r.ev.WriteSpecStart(report, specReport) + r.ev.WriteSpecOut(report, specReport) + r.ev.WriteSpecResult(report, specReport) + } else { + // handle any other leaf node as generic output + r.ev.WriteSpecOut(report, specReport) + } + } + r.ev.WriteSuiteResult(report) + return nil +} diff --git a/vendor/github.com/onsi/ginkgo/v2/reporters/gojson_report.go b/vendor/github.com/onsi/ginkgo/v2/reporters/gojson_report.go new file mode 100644 index 000000000..d02fb7a1a --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/reporters/gojson_report.go @@ -0,0 +1,61 @@ +package reporters + +import ( + "encoding/json" + "fmt" + "os" + "path" + + "github.com/onsi/ginkgo/v2/internal/reporters" + "github.com/onsi/ginkgo/v2/types" +) + +// GenerateGoTestJSONReport produces a JSON-formatted in the test2json format used by `go test -json` +func GenerateGoTestJSONReport(report types.Report, destination string) error { + // walk report and generate test2json-compatible objects + // JSON-encode the objects into filename + if err := os.MkdirAll(path.Dir(destination), 0770); err != nil { + return err + } + f, err := os.Create(destination) + if err != nil { + return err + } + defer f.Close() + enc := json.NewEncoder(f) + r := reporters.NewGoJSONReporter( + enc, + systemErrForUnstructuredReporters, + systemOutForUnstructuredReporters, + ) + return r.Write(report) +} + +// MergeJSONReports produces a single JSON-formatted report at the passed in destination by merging the JSON-formatted reports provided in sources +// It skips over reports that fail to decode but reports on them via the returned messages []string +func MergeAndCleanupGoTestJSONReports(sources []string, destination string) ([]string, error) { + messages := []string{} + if err := os.MkdirAll(path.Dir(destination), 0770); err != nil { + return messages, err + } + f, err := os.Create(destination) + if err != nil { + return messages, err + } + defer f.Close() + + for _, source := range sources { + data, err := os.ReadFile(source) + if err != nil { + messages = append(messages, fmt.Sprintf("Could not open %s:\n%s", source, err.Error())) + continue + } + _, err = f.Write(data) + if err != nil { + messages = append(messages, fmt.Sprintf("Could not write to %s:\n%s", destination, err.Error())) + continue + } + os.Remove(source) + } + return messages, nil +} diff --git a/vendor/github.com/onsi/ginkgo/v2/reporting_dsl.go b/vendor/github.com/onsi/ginkgo/v2/reporting_dsl.go index 5bf2e62e9..dd135054d 100644 --- a/vendor/github.com/onsi/ginkgo/v2/reporting_dsl.go +++ b/vendor/github.com/onsi/ginkgo/v2/reporting_dsl.go @@ -165,7 +165,7 @@ ReportAfterSuite nodes must be created at the top-level (i.e. not nested in a Co When running in parallel, Ginkgo ensures that only one of the parallel nodes runs the ReportAfterSuite and that it is passed a report that is aggregated across all parallel nodes -In addition to using ReportAfterSuite to programmatically generate suite reports, you can also generate JSON, JUnit, and Teamcity formatted reports using the --json-report, --junit-report, and --teamcity-report ginkgo CLI flags. +In addition to using ReportAfterSuite to programmatically generate suite reports, you can also generate JSON, GoJSON, JUnit, and Teamcity formatted reports using the --json-report, --gojson-report, --junit-report, and --teamcity-report ginkgo CLI flags. You cannot nest any other Ginkgo nodes within a ReportAfterSuite node's closure. You can learn more about ReportAfterSuite here: https://onsi.github.io/ginkgo/#generating-reports-programmatically @@ -188,6 +188,12 @@ func registerReportAfterSuiteNodeForAutogeneratedReports(reporterConfig types.Re Fail(fmt.Sprintf("Failed to generate JSON report:\n%s", err.Error())) } } + if reporterConfig.GoJSONReport != "" { + err := reporters.GenerateGoTestJSONReport(report, reporterConfig.GoJSONReport) + if err != nil { + Fail(fmt.Sprintf("Failed to generate Go JSON report:\n%s", err.Error())) + } + } if reporterConfig.JUnitReport != "" { err := reporters.GenerateJUnitReport(report, reporterConfig.JUnitReport) if err != nil { @@ -206,6 +212,9 @@ func registerReportAfterSuiteNodeForAutogeneratedReports(reporterConfig types.Re if reporterConfig.JSONReport != "" { flags = append(flags, "--json-report") } + if reporterConfig.GoJSONReport != "" { + flags = append(flags, "--gojson-report") + } if reporterConfig.JUnitReport != "" { flags = append(flags, "--junit-report") } diff --git a/vendor/github.com/onsi/ginkgo/v2/types/config.go b/vendor/github.com/onsi/ginkgo/v2/types/config.go index b99a9e15e..f84703604 100644 --- a/vendor/github.com/onsi/ginkgo/v2/types/config.go +++ b/vendor/github.com/onsi/ginkgo/v2/types/config.go @@ -96,6 +96,7 @@ type ReporterConfig struct { ForceNewlines bool JSONReport string + GoJSONReport string JUnitReport string TeamcityReport string } @@ -112,7 +113,7 @@ func (rc ReporterConfig) Verbosity() VerbosityLevel { } func (rc ReporterConfig) WillGenerateReport() bool { - return rc.JSONReport != "" || rc.JUnitReport != "" || rc.TeamcityReport != "" + return rc.JSONReport != "" || rc.GoJSONReport != "" || rc.JUnitReport != "" || rc.TeamcityReport != "" } func NewDefaultReporterConfig() ReporterConfig { @@ -359,6 +360,8 @@ var ReporterConfigFlags = GinkgoFlags{ {KeyPath: "R.JSONReport", Name: "json-report", UsageArgument: "filename.json", SectionKey: "output", Usage: "If set, Ginkgo will generate a JSON-formatted test report at the specified location."}, + {KeyPath: "R.GoJSONReport", Name: "gojson-report", UsageArgument: "filename.json", SectionKey: "output", + Usage: "If set, Ginkgo will generate a Go JSON-formatted test report at the specified location."}, {KeyPath: "R.JUnitReport", Name: "junit-report", UsageArgument: "filename.xml", SectionKey: "output", DeprecatedName: "reportFile", DeprecatedDocLink: "improved-reporting-infrastructure", Usage: "If set, Ginkgo will generate a conformant junit test report in the specified file."}, {KeyPath: "R.TeamcityReport", Name: "teamcity-report", UsageArgument: "filename", SectionKey: "output", diff --git a/vendor/github.com/onsi/ginkgo/v2/types/version.go b/vendor/github.com/onsi/ginkgo/v2/types/version.go index 6aca6efa8..a30e68be1 100644 --- a/vendor/github.com/onsi/ginkgo/v2/types/version.go +++ b/vendor/github.com/onsi/ginkgo/v2/types/version.go @@ -1,3 +1,3 @@ package types -const VERSION = "2.25.3" +const VERSION = "2.26.0" diff --git a/vendor/golang.org/x/xerrors/LICENSE b/vendor/golang.org/x/xerrors/LICENSE deleted file mode 100644 index e4a47e17f..000000000 --- a/vendor/golang.org/x/xerrors/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2019 The Go Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/golang.org/x/xerrors/PATENTS b/vendor/golang.org/x/xerrors/PATENTS deleted file mode 100644 index 733099041..000000000 --- a/vendor/golang.org/x/xerrors/PATENTS +++ /dev/null @@ -1,22 +0,0 @@ -Additional IP Rights Grant (Patents) - -"This implementation" means the copyrightable works distributed by -Google as part of the Go project. - -Google hereby grants to You a perpetual, worldwide, non-exclusive, -no-charge, royalty-free, irrevocable (except as stated in this section) -patent license to make, have made, use, offer to sell, sell, import, -transfer and otherwise run, modify and propagate the contents of this -implementation of Go, where such license applies only to those patent -claims, both currently owned or controlled by Google and acquired in -the future, licensable by Google that are necessarily infringed by this -implementation of Go. This grant does not include claims that would be -infringed only as a consequence of further modification of this -implementation. If you or your agent or exclusive licensee institute or -order or agree to the institution of patent litigation against any -entity (including a cross-claim or counterclaim in a lawsuit) alleging -that this implementation of Go or any code incorporated within this -implementation of Go constitutes direct or contributory patent -infringement, or inducement of patent infringement, then any patent -rights granted to you under this License for this implementation of Go -shall terminate as of the date such litigation is filed. diff --git a/vendor/golang.org/x/xerrors/README b/vendor/golang.org/x/xerrors/README deleted file mode 100644 index aac7867a5..000000000 --- a/vendor/golang.org/x/xerrors/README +++ /dev/null @@ -1,2 +0,0 @@ -This repository holds the transition packages for the new Go 1.13 error values. -See golang.org/design/29934-error-values. diff --git a/vendor/golang.org/x/xerrors/adaptor.go b/vendor/golang.org/x/xerrors/adaptor.go deleted file mode 100644 index 4317f2483..000000000 --- a/vendor/golang.org/x/xerrors/adaptor.go +++ /dev/null @@ -1,193 +0,0 @@ -// Copyright 2018 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package xerrors - -import ( - "bytes" - "fmt" - "io" - "reflect" - "strconv" -) - -// FormatError calls the FormatError method of f with an errors.Printer -// configured according to s and verb, and writes the result to s. -func FormatError(f Formatter, s fmt.State, verb rune) { - // Assuming this function is only called from the Format method, and given - // that FormatError takes precedence over Format, it cannot be called from - // any package that supports errors.Formatter. It is therefore safe to - // disregard that State may be a specific printer implementation and use one - // of our choice instead. - - // limitations: does not support printing error as Go struct. - - var ( - sep = " " // separator before next error - p = &state{State: s} - direct = true - ) - - var err error = f - - switch verb { - // Note that this switch must match the preference order - // for ordinary string printing (%#v before %+v, and so on). - - case 'v': - if s.Flag('#') { - if stringer, ok := err.(fmt.GoStringer); ok { - io.WriteString(&p.buf, stringer.GoString()) - goto exit - } - // proceed as if it were %v - } else if s.Flag('+') { - p.printDetail = true - sep = "\n - " - } - case 's': - case 'q', 'x', 'X': - // Use an intermediate buffer in the rare cases that precision, - // truncation, or one of the alternative verbs (q, x, and X) are - // specified. - direct = false - - default: - p.buf.WriteString("%!") - p.buf.WriteRune(verb) - p.buf.WriteByte('(') - switch { - case err != nil: - p.buf.WriteString(reflect.TypeOf(f).String()) - default: - p.buf.WriteString("") - } - p.buf.WriteByte(')') - io.Copy(s, &p.buf) - return - } - -loop: - for { - switch v := err.(type) { - case Formatter: - err = v.FormatError((*printer)(p)) - case fmt.Formatter: - v.Format(p, 'v') - break loop - default: - io.WriteString(&p.buf, v.Error()) - break loop - } - if err == nil { - break - } - if p.needColon || !p.printDetail { - p.buf.WriteByte(':') - p.needColon = false - } - p.buf.WriteString(sep) - p.inDetail = false - p.needNewline = false - } - -exit: - width, okW := s.Width() - prec, okP := s.Precision() - - if !direct || (okW && width > 0) || okP { - // Construct format string from State s. - format := []byte{'%'} - if s.Flag('-') { - format = append(format, '-') - } - if s.Flag('+') { - format = append(format, '+') - } - if s.Flag(' ') { - format = append(format, ' ') - } - if okW { - format = strconv.AppendInt(format, int64(width), 10) - } - if okP { - format = append(format, '.') - format = strconv.AppendInt(format, int64(prec), 10) - } - format = append(format, string(verb)...) - fmt.Fprintf(s, string(format), p.buf.String()) - } else { - io.Copy(s, &p.buf) - } -} - -var detailSep = []byte("\n ") - -// state tracks error printing state. It implements fmt.State. -type state struct { - fmt.State - buf bytes.Buffer - - printDetail bool - inDetail bool - needColon bool - needNewline bool -} - -func (s *state) Write(b []byte) (n int, err error) { - if s.printDetail { - if len(b) == 0 { - return 0, nil - } - if s.inDetail && s.needColon { - s.needNewline = true - if b[0] == '\n' { - b = b[1:] - } - } - k := 0 - for i, c := range b { - if s.needNewline { - if s.inDetail && s.needColon { - s.buf.WriteByte(':') - s.needColon = false - } - s.buf.Write(detailSep) - s.needNewline = false - } - if c == '\n' { - s.buf.Write(b[k:i]) - k = i + 1 - s.needNewline = true - } - } - s.buf.Write(b[k:]) - if !s.inDetail { - s.needColon = true - } - } else if !s.inDetail { - s.buf.Write(b) - } - return len(b), nil -} - -// printer wraps a state to implement an xerrors.Printer. -type printer state - -func (s *printer) Print(args ...interface{}) { - if !s.inDetail || s.printDetail { - fmt.Fprint((*state)(s), args...) - } -} - -func (s *printer) Printf(format string, args ...interface{}) { - if !s.inDetail || s.printDetail { - fmt.Fprintf((*state)(s), format, args...) - } -} - -func (s *printer) Detail() bool { - s.inDetail = true - return s.printDetail -} diff --git a/vendor/golang.org/x/xerrors/codereview.cfg b/vendor/golang.org/x/xerrors/codereview.cfg deleted file mode 100644 index 3f8b14b64..000000000 --- a/vendor/golang.org/x/xerrors/codereview.cfg +++ /dev/null @@ -1 +0,0 @@ -issuerepo: golang/go diff --git a/vendor/golang.org/x/xerrors/doc.go b/vendor/golang.org/x/xerrors/doc.go deleted file mode 100644 index 2ef99f5a8..000000000 --- a/vendor/golang.org/x/xerrors/doc.go +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2019 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package xerrors implements functions to manipulate errors. -// -// This package is based on the Go 2 proposal for error values: -// -// https://golang.org/design/29934-error-values -// -// These functions were incorporated into the standard library's errors package -// in Go 1.13: -// - Is -// - As -// - Unwrap -// -// Also, Errorf's %w verb was incorporated into fmt.Errorf. -// -// Use this package to get equivalent behavior in all supported Go versions. -// -// No other features of this package were included in Go 1.13, and at present -// there are no plans to include any of them. -package xerrors // import "golang.org/x/xerrors" diff --git a/vendor/golang.org/x/xerrors/errors.go b/vendor/golang.org/x/xerrors/errors.go deleted file mode 100644 index e88d3772d..000000000 --- a/vendor/golang.org/x/xerrors/errors.go +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package xerrors - -import "fmt" - -// errorString is a trivial implementation of error. -type errorString struct { - s string - frame Frame -} - -// New returns an error that formats as the given text. -// -// The returned error contains a Frame set to the caller's location and -// implements Formatter to show this information when printed with details. -func New(text string) error { - return &errorString{text, Caller(1)} -} - -func (e *errorString) Error() string { - return e.s -} - -func (e *errorString) Format(s fmt.State, v rune) { FormatError(e, s, v) } - -func (e *errorString) FormatError(p Printer) (next error) { - p.Print(e.s) - e.frame.Format(p) - return nil -} diff --git a/vendor/golang.org/x/xerrors/fmt.go b/vendor/golang.org/x/xerrors/fmt.go deleted file mode 100644 index 27a5d70bd..000000000 --- a/vendor/golang.org/x/xerrors/fmt.go +++ /dev/null @@ -1,190 +0,0 @@ -// Copyright 2018 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package xerrors - -import ( - "fmt" - "strings" - "unicode" - "unicode/utf8" - - "golang.org/x/xerrors/internal" -) - -const percentBangString = "%!" - -// Errorf formats according to a format specifier and returns the string as a -// value that satisfies error. -// -// The returned error includes the file and line number of the caller when -// formatted with additional detail enabled. If the last argument is an error -// the returned error's Format method will return it if the format string ends -// with ": %s", ": %v", or ": %w". If the last argument is an error and the -// format string ends with ": %w", the returned error implements an Unwrap -// method returning it. -// -// If the format specifier includes a %w verb with an error operand in a -// position other than at the end, the returned error will still implement an -// Unwrap method returning the operand, but the error's Format method will not -// return the wrapped error. -// -// It is invalid to include more than one %w verb or to supply it with an -// operand that does not implement the error interface. The %w verb is otherwise -// a synonym for %v. -// -// Note that as of Go 1.13, the fmt.Errorf function will do error formatting, -// but it will not capture a stack backtrace. -func Errorf(format string, a ...interface{}) error { - format = formatPlusW(format) - // Support a ": %[wsv]" suffix, which works well with xerrors.Formatter. - wrap := strings.HasSuffix(format, ": %w") - idx, format2, ok := parsePercentW(format) - percentWElsewhere := !wrap && idx >= 0 - if !percentWElsewhere && (wrap || strings.HasSuffix(format, ": %s") || strings.HasSuffix(format, ": %v")) { - err := errorAt(a, len(a)-1) - if err == nil { - return &noWrapError{fmt.Sprintf(format, a...), nil, Caller(1)} - } - // TODO: this is not entirely correct. The error value could be - // printed elsewhere in format if it mixes numbered with unnumbered - // substitutions. With relatively small changes to doPrintf we can - // have it optionally ignore extra arguments and pass the argument - // list in its entirety. - msg := fmt.Sprintf(format[:len(format)-len(": %s")], a[:len(a)-1]...) - frame := Frame{} - if internal.EnableTrace { - frame = Caller(1) - } - if wrap { - return &wrapError{msg, err, frame} - } - return &noWrapError{msg, err, frame} - } - // Support %w anywhere. - // TODO: don't repeat the wrapped error's message when %w occurs in the middle. - msg := fmt.Sprintf(format2, a...) - if idx < 0 { - return &noWrapError{msg, nil, Caller(1)} - } - err := errorAt(a, idx) - if !ok || err == nil { - // Too many %ws or argument of %w is not an error. Approximate the Go - // 1.13 fmt.Errorf message. - return &noWrapError{fmt.Sprintf("%sw(%s)", percentBangString, msg), nil, Caller(1)} - } - frame := Frame{} - if internal.EnableTrace { - frame = Caller(1) - } - return &wrapError{msg, err, frame} -} - -func errorAt(args []interface{}, i int) error { - if i < 0 || i >= len(args) { - return nil - } - err, ok := args[i].(error) - if !ok { - return nil - } - return err -} - -// formatPlusW is used to avoid the vet check that will barf at %w. -func formatPlusW(s string) string { - return s -} - -// Return the index of the only %w in format, or -1 if none. -// Also return a rewritten format string with %w replaced by %v, and -// false if there is more than one %w. -// TODO: handle "%[N]w". -func parsePercentW(format string) (idx int, newFormat string, ok bool) { - // Loosely copied from golang.org/x/tools/go/analysis/passes/printf/printf.go. - idx = -1 - ok = true - n := 0 - sz := 0 - var isW bool - for i := 0; i < len(format); i += sz { - if format[i] != '%' { - sz = 1 - continue - } - // "%%" is not a format directive. - if i+1 < len(format) && format[i+1] == '%' { - sz = 2 - continue - } - sz, isW = parsePrintfVerb(format[i:]) - if isW { - if idx >= 0 { - ok = false - } else { - idx = n - } - // "Replace" the last character, the 'w', with a 'v'. - p := i + sz - 1 - format = format[:p] + "v" + format[p+1:] - } - n++ - } - return idx, format, ok -} - -// Parse the printf verb starting with a % at s[0]. -// Return how many bytes it occupies and whether the verb is 'w'. -func parsePrintfVerb(s string) (int, bool) { - // Assume only that the directive is a sequence of non-letters followed by a single letter. - sz := 0 - var r rune - for i := 1; i < len(s); i += sz { - r, sz = utf8.DecodeRuneInString(s[i:]) - if unicode.IsLetter(r) { - return i + sz, r == 'w' - } - } - return len(s), false -} - -type noWrapError struct { - msg string - err error - frame Frame -} - -func (e *noWrapError) Error() string { - return fmt.Sprint(e) -} - -func (e *noWrapError) Format(s fmt.State, v rune) { FormatError(e, s, v) } - -func (e *noWrapError) FormatError(p Printer) (next error) { - p.Print(e.msg) - e.frame.Format(p) - return e.err -} - -type wrapError struct { - msg string - err error - frame Frame -} - -func (e *wrapError) Error() string { - return fmt.Sprint(e) -} - -func (e *wrapError) Format(s fmt.State, v rune) { FormatError(e, s, v) } - -func (e *wrapError) FormatError(p Printer) (next error) { - p.Print(e.msg) - e.frame.Format(p) - return e.err -} - -func (e *wrapError) Unwrap() error { - return e.err -} diff --git a/vendor/golang.org/x/xerrors/format.go b/vendor/golang.org/x/xerrors/format.go deleted file mode 100644 index 1bc9c26b9..000000000 --- a/vendor/golang.org/x/xerrors/format.go +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2018 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package xerrors - -// A Formatter formats error messages. -type Formatter interface { - error - - // FormatError prints the receiver's first error and returns the next error in - // the error chain, if any. - FormatError(p Printer) (next error) -} - -// A Printer formats error messages. -// -// The most common implementation of Printer is the one provided by package fmt -// during Printf (as of Go 1.13). Localization packages such as golang.org/x/text/message -// typically provide their own implementations. -type Printer interface { - // Print appends args to the message output. - Print(args ...interface{}) - - // Printf writes a formatted string. - Printf(format string, args ...interface{}) - - // Detail reports whether error detail is requested. - // After the first call to Detail, all text written to the Printer - // is formatted as additional detail, or ignored when - // detail has not been requested. - // If Detail returns false, the caller can avoid printing the detail at all. - Detail() bool -} diff --git a/vendor/golang.org/x/xerrors/frame.go b/vendor/golang.org/x/xerrors/frame.go deleted file mode 100644 index 0de628ec5..000000000 --- a/vendor/golang.org/x/xerrors/frame.go +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2018 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package xerrors - -import ( - "runtime" -) - -// A Frame contains part of a call stack. -type Frame struct { - // Make room for three PCs: the one we were asked for, what it called, - // and possibly a PC for skipPleaseUseCallersFrames. See: - // https://go.googlesource.com/go/+/032678e0fb/src/runtime/extern.go#169 - frames [3]uintptr -} - -// Caller returns a Frame that describes a frame on the caller's stack. -// The argument skip is the number of frames to skip over. -// Caller(0) returns the frame for the caller of Caller. -func Caller(skip int) Frame { - var s Frame - runtime.Callers(skip+1, s.frames[:]) - return s -} - -// location reports the file, line, and function of a frame. -// -// The returned function may be "" even if file and line are not. -func (f Frame) location() (function, file string, line int) { - frames := runtime.CallersFrames(f.frames[:]) - if _, ok := frames.Next(); !ok { - return "", "", 0 - } - fr, ok := frames.Next() - if !ok { - return "", "", 0 - } - return fr.Function, fr.File, fr.Line -} - -// Format prints the stack as error detail. -// It should be called from an error's Format implementation -// after printing any other error detail. -func (f Frame) Format(p Printer) { - if p.Detail() { - function, file, line := f.location() - if function != "" { - p.Printf("%s\n ", function) - } - if file != "" { - p.Printf("%s:%d\n", file, line) - } - } -} diff --git a/vendor/golang.org/x/xerrors/internal/internal.go b/vendor/golang.org/x/xerrors/internal/internal.go deleted file mode 100644 index 89f4eca5d..000000000 --- a/vendor/golang.org/x/xerrors/internal/internal.go +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright 2018 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package internal - -// EnableTrace indicates whether stack information should be recorded in errors. -var EnableTrace = true diff --git a/vendor/golang.org/x/xerrors/wrap.go b/vendor/golang.org/x/xerrors/wrap.go deleted file mode 100644 index 9842758ca..000000000 --- a/vendor/golang.org/x/xerrors/wrap.go +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright 2018 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package xerrors - -import ( - "reflect" -) - -// A Wrapper provides context around another error. -type Wrapper interface { - // Unwrap returns the next error in the error chain. - // If there is no next error, Unwrap returns nil. - Unwrap() error -} - -// Opaque returns an error with the same error formatting as err -// but that does not match err and cannot be unwrapped. -func Opaque(err error) error { - return noWrapper{err} -} - -type noWrapper struct { - error -} - -func (e noWrapper) FormatError(p Printer) (next error) { - if f, ok := e.error.(Formatter); ok { - return f.FormatError(p) - } - p.Print(e.error) - return nil -} - -// Unwrap returns the result of calling the Unwrap method on err, if err implements -// Unwrap. Otherwise, Unwrap returns nil. -// -// Deprecated: As of Go 1.13, use errors.Unwrap instead. -func Unwrap(err error) error { - u, ok := err.(Wrapper) - if !ok { - return nil - } - return u.Unwrap() -} - -// Is reports whether any error in err's chain matches target. -// -// An error is considered to match a target if it is equal to that target or if -// it implements a method Is(error) bool such that Is(target) returns true. -// -// Deprecated: As of Go 1.13, use errors.Is instead. -func Is(err, target error) bool { - if target == nil { - return err == target - } - - isComparable := reflect.TypeOf(target).Comparable() - for { - if isComparable && err == target { - return true - } - if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) { - return true - } - // TODO: consider supporing target.Is(err). This would allow - // user-definable predicates, but also may allow for coping with sloppy - // APIs, thereby making it easier to get away with them. - if err = Unwrap(err); err == nil { - return false - } - } -} - -// As finds the first error in err's chain that matches the type to which target -// points, and if so, sets the target to its value and returns true. An error -// matches a type if it is assignable to the target type, or if it has a method -// As(interface{}) bool such that As(target) returns true. As will panic if target -// is not a non-nil pointer to a type which implements error or is of interface type. -// -// The As method should set the target to its value and return true if err -// matches the type to which target points. -// -// Deprecated: As of Go 1.13, use errors.As instead. -func As(err error, target interface{}) bool { - if target == nil { - panic("errors: target cannot be nil") - } - val := reflect.ValueOf(target) - typ := val.Type() - if typ.Kind() != reflect.Ptr || val.IsNil() { - panic("errors: target must be a non-nil pointer") - } - if e := typ.Elem(); e.Kind() != reflect.Interface && !e.Implements(errorType) { - panic("errors: *target must be interface or implement error") - } - targetType := typ.Elem() - for err != nil { - if reflect.TypeOf(err).AssignableTo(targetType) { - val.Elem().Set(reflect.ValueOf(err)) - return true - } - if x, ok := err.(interface{ As(interface{}) bool }); ok && x.As(target) { - return true - } - err = Unwrap(err) - } - return false -} - -var errorType = reflect.TypeOf((*error)(nil)).Elem() diff --git a/vendor/modules.txt b/vendor/modules.txt index 9908bdbd3..b3ed3ecac 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -679,11 +679,12 @@ github.com/goccy/go-json/internal/encoder/vm_color_indent github.com/goccy/go-json/internal/encoder/vm_indent github.com/goccy/go-json/internal/errors github.com/goccy/go-json/internal/runtime -# github.com/goccy/go-yaml v1.12.0 -## explicit; go 1.19 +# github.com/goccy/go-yaml v1.18.0 +## explicit; go 1.21.0 github.com/goccy/go-yaml github.com/goccy/go-yaml/ast github.com/goccy/go-yaml/internal/errors +github.com/goccy/go-yaml/internal/format github.com/goccy/go-yaml/lexer github.com/goccy/go-yaml/parser github.com/goccy/go-yaml/printer @@ -1199,7 +1200,7 @@ github.com/onsi/ginkgo/reporters/stenographer github.com/onsi/ginkgo/reporters/stenographer/support/go-colorable github.com/onsi/ginkgo/reporters/stenographer/support/go-isatty github.com/onsi/ginkgo/types -# github.com/onsi/ginkgo/v2 v2.25.3 +# github.com/onsi/ginkgo/v2 v2.26.0 ## explicit; go 1.23.0 github.com/onsi/ginkgo/v2 github.com/onsi/ginkgo/v2/config @@ -1218,6 +1219,7 @@ github.com/onsi/ginkgo/v2/internal github.com/onsi/ginkgo/v2/internal/global github.com/onsi/ginkgo/v2/internal/interrupt_handler github.com/onsi/ginkgo/v2/internal/parallel_support +github.com/onsi/ginkgo/v2/internal/reporters github.com/onsi/ginkgo/v2/internal/testingtproxy github.com/onsi/ginkgo/v2/reporters github.com/onsi/ginkgo/v2/types @@ -2498,10 +2500,6 @@ golang.org/x/tools/internal/stdlib golang.org/x/tools/internal/typeparams golang.org/x/tools/internal/typesinternal golang.org/x/tools/internal/versions -# golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 -## explicit; go 1.17 -golang.org/x/xerrors -golang.org/x/xerrors/internal # google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb ## explicit; go 1.23.0 google.golang.org/genproto/protobuf/field_mask