Repository: google/yamlfmt (go)

Issue:
Basic formatter spuriously deletes backslashes in strings after glob run
When running yamlfmt basic formatting with a config file and targeting several paths in a repository, the formatter spuriously deletes backslashes and thereby breaks the semantics of those YAML files. For example

```yaml
outer:
    - inner: "python3 -m some.module \\${VARIABLE_:-}" 
```

becomes 

```yaml
outer:
    - inner: "python3 -m some.module ${VARIABLE_:-}" 
```

and

```yaml
outer:
  - name: something
    regex: '\(\d+, "Unknown database .*'
```

becomes 

```yaml
outer:
  - name: something
    regex: '(d+, "Unknown database .*'
```

which are not semantically identical. Strangely enough, targeting these individual files with the formatter doesn't change them this way. Only running the formatter over a glob of YAML paths in my directory has this effect.

Unfortunately, I can't attach the entire directory as it's proprietary. But I'm hoping you have some insights as to why this might happen. I'll try to create a sanitized reproduction if possible.

Pull request description:
hotfix: stop unicode from removing backslashes
Closes #53 

Due to a logic oversight in the unicode parser, backslashes weren't being overwritten in scenarios where the backslash wasn't part of a unicode point representation. This PR changes it so the point is only parsed if it sees \ and U at once, otherwise writing the \ as normal.

Patch:
diff --git a/.gitignore b/.gitignore
index 2bf3c64..1aebf3a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,9 @@
+# Sometimes use these for testing
 .yamlfmt
+tmp
+
+# Goreleaser build folder
 dist/
 
+# vscode settings
 .vscode
diff --git a/internal/hotfix/unicode.go b/internal/hotfix/unicode.go
index 49acdd2..4374eaf 100644
--- a/internal/hotfix/unicode.go
+++ b/internal/hotfix/unicode.go
@@ -34,7 +34,7 @@ func ParseUnicodePoints(content []byte) ([]byte, error) {
 
 	var err error
 	for err != errEndOfBuffer {
-		if p.peek() == '\\' {
+		if p.peek() == '\\' && p.peekAhead(1) == 'U' {
 			err = p.parseUTF8CodePoint()
 			continue
 		}
@@ -49,7 +49,7 @@ func ParseUnicodePoints(content []byte) ([]byte, error) {
 type unicodeParser struct {
 	buf []byte
 	out []byte
-	pos int
+	pos uint
 }
 
 var (
@@ -61,6 +61,10 @@ func (p *unicodeParser) peek() byte {
 	return p.buf[p.pos]
 }
 
+func (p *unicodeParser) peekAhead(n uint) byte {
+	return p.buf[p.pos+n]
+}
+
 func (p *unicodeParser) write() {
 	p.out = append(p.out, p.peek())
 }
@@ -71,7 +75,7 @@ func (p *unicodeParser) writeArbitrary(b []byte) {
 
 func (p *unicodeParser) next() error {
 	p.pos++
-	if p.pos == len(p.buf) {
+	if p.pos == uint(len(p.buf)) {
 		return errEndOfBuffer
 	}
 	return nil


Test patch:
diff --git a/internal/hotfix/unicode_test.go b/internal/hotfix/unicode_test.go
index 1e9814e..35a4979 100644
--- a/internal/hotfix/unicode_test.go
+++ b/internal/hotfix/unicode_test.go
@@ -37,6 +37,11 @@ func TestParseEmoji(t *testing.T) {
 			yamlStr:     "a: 😼 👑\n",
 			expectedStr: "a: \"😼 👑\"\n",
 		},
+		{
+			name:        "retains backslashes",
+			yamlStr:     "a: \\look at my backslash\n",
+			expectedStr: "a: \\look at my backslash\n",
+		},
 	}
 
 	f := &basic.BasicFormatter{Config: basic.DefaultConfig()}