-
Notifications
You must be signed in to change notification settings - Fork 316
refactor: drop dependency on github.com/fatih/structtag #1650
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
alexandear
wants to merge
7
commits into
mgechev:master
Choose a base branch
from
alexandear:refactor/remove-fatih-structtag
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+349
−6
Open
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
e4f9945
refactor: drop dependency on github.com/fatih/structtag
alexandear f8fe9b6
Remove String and Value
alexandear 9fce9bd
fix test duplication
alexandear 6823e8a
Update internal/structtag/structtag.go
alexandear 5ee3807
implement Copilot suggestion
alexandear 1c166cf
remove redundant if len check
alexandear 23a0ba7
review
alexandear File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,119 @@ | ||
| // Package structtag provides utilities to parse and manipulate struct tags. | ||
| package structtag | ||
|
|
||
| import ( | ||
| "errors" | ||
| "strconv" | ||
| "strings" | ||
| ) | ||
|
|
||
| var errValueSyntax = errors.New("invalid syntax for struct tag value") | ||
|
|
||
| // Tag defines a single struct's string literal tag. | ||
| type Tag struct { | ||
| // Key is the tag key, such as json, xml, etc. | ||
| // i.e., `json:"foo,omitempty"`. Here Key is "json". | ||
| Key string | ||
|
|
||
| // Name is the tag name. | ||
| // i.e., `json:"foo,omitempty"`. Here Name is "foo". | ||
| Name string | ||
|
|
||
| // Options are the tag options. | ||
| // i.e., `json:"foo,omitempty"`. Here Options is ["omitempty"]. | ||
| Options []string | ||
| } | ||
|
|
||
| // Parse parses a single struct field tag and returns the set of tags. | ||
| func Parse(tag string) ([]*Tag, error) { | ||
| if tag == "" { | ||
| return nil, nil | ||
| } | ||
|
|
||
| var tags []*Tag | ||
|
|
||
| for tag != "" { | ||
| // Skip leading ASCII whitespace to match scanKey's delimiter rules. | ||
| for tag != "" && tag[0] <= ' ' { | ||
| tag = tag[1:] | ||
| } | ||
| if tag == "" { | ||
| break | ||
| } | ||
|
|
||
| i := scanKey(tag) | ||
| if i == 0 { | ||
| return nil, errors.New("invalid syntax for struct tag key") | ||
| } | ||
| if i+1 >= len(tag) || tag[i] != ':' { | ||
| return nil, errors.New("invalid syntax for struct tag pair") | ||
| } | ||
| if tag[i+1] != '"' { | ||
| return nil, errValueSyntax | ||
| } | ||
|
|
||
| key := tag[:i] | ||
| tag = tag[i+1:] | ||
|
|
||
| i, qvalue, err := scanValue(tag) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| value, err := strconv.Unquote(qvalue) | ||
| if err != nil { | ||
| return nil, errValueSyntax | ||
| } | ||
|
|
||
| name, options := parseTagValue(value) | ||
| tags = append(tags, &Tag{ | ||
| Key: key, | ||
| Name: name, | ||
| Options: options, | ||
| }) | ||
|
|
||
| tag = tag[i:] | ||
| } | ||
|
|
||
| return tags, nil | ||
| } | ||
|
|
||
| // scanKey finds the position of the colon that ends the tag key. | ||
| // It returns the index of the first invalid character (space, colon, quote, or control character), | ||
| // or len(tag) if all characters are valid. | ||
| func scanKey(tag string) int { | ||
| for i := 0; i < len(tag); i++ { | ||
| if tag[i] <= ' ' || tag[i] == ':' || tag[i] == '"' || tag[i] == 0x7f { | ||
| return i | ||
| } | ||
| } | ||
| return len(tag) | ||
| } | ||
|
|
||
| // scanValue scans a quoted string value and returns its index and quoted content. | ||
| // The tag string must start with a double-quote character. | ||
| func scanValue(tag string) (idx int, qvalue string, err error) { | ||
| // Find closing quote, handling escapes. | ||
| i := 1 | ||
| for i < len(tag) && tag[i] != '"' { | ||
| if tag[i] == '\\' { | ||
| i++ | ||
| } | ||
| i++ | ||
| } | ||
| if i >= len(tag) { | ||
| return 0, "", errValueSyntax | ||
| } | ||
| return i + 1, tag[:i+1], nil | ||
| } | ||
|
|
||
| // parseTagValue parses an unquoted tag value into name and options. | ||
| // The format is "name" or "name,opt1,opt2,...". | ||
| func parseTagValue(value string) (name string, options []string) { | ||
| parts := strings.Split(value, ",") | ||
| name = parts[0] | ||
| if len(parts) > 1 { | ||
| options = parts[1:] | ||
| } | ||
| return name, options | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,228 @@ | ||
| package structtag_test | ||
|
|
||
| import ( | ||
| "errors" | ||
| "reflect" | ||
| "testing" | ||
|
|
||
| "github.com/mgechev/revive/internal/structtag" | ||
| ) | ||
|
|
||
| func TestParse(t *testing.T) { | ||
| test := []struct { | ||
| name string | ||
| tag string | ||
| want []*structtag.Tag | ||
| wantErr error | ||
| }{ | ||
| { | ||
| name: "empty tag", | ||
| tag: "", | ||
| want: nil, | ||
| }, | ||
| { | ||
| name: "tag with only whitespace", | ||
| tag: " ", | ||
| want: nil, | ||
| }, | ||
| { | ||
| name: "tag with leading tabs and newlines", | ||
| tag: "\tjson:\"foo\"\n\txml:\"bar\"", | ||
| want: []*structtag.Tag{ | ||
| { | ||
| Key: "json", | ||
| Name: "foo", | ||
| }, | ||
| { | ||
| Key: "xml", | ||
| Name: "bar", | ||
| }, | ||
| }, | ||
| }, | ||
| { | ||
| name: "tag with one key (valid)", | ||
| tag: `json:""`, | ||
| want: []*structtag.Tag{ | ||
| { | ||
| Key: "json", | ||
| }, | ||
| }, | ||
| }, | ||
| { | ||
| name: "tag with one key and dash name", | ||
| tag: `json:"-"`, | ||
| want: []*structtag.Tag{ | ||
| { | ||
| Key: "json", | ||
| Name: "-", | ||
| }, | ||
| }, | ||
| }, | ||
| { | ||
| name: "tag with key and name", | ||
| tag: `json:"foo"`, | ||
| want: []*structtag.Tag{ | ||
| { | ||
| Key: "json", | ||
| Name: "foo", | ||
| }, | ||
| }, | ||
| }, | ||
| { | ||
| name: "tag with key, name and option", | ||
| tag: `json:"foo,omitempty"`, | ||
| want: []*structtag.Tag{ | ||
| { | ||
| Key: "json", | ||
| Name: "foo", | ||
| Options: []string{"omitempty"}, | ||
| }, | ||
| }, | ||
| }, | ||
| { | ||
| name: "tag with multiple keys", | ||
| tag: `json:"" hcl:""`, | ||
| want: []*structtag.Tag{ | ||
| { | ||
| Key: "json", | ||
| }, | ||
| { | ||
| Key: "hcl", | ||
| }, | ||
| }, | ||
| }, | ||
| { | ||
| name: "tag with multiple keys and names", | ||
| tag: `json:"foo" hcl:"foo"`, | ||
| want: []*structtag.Tag{ | ||
alexandear marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| { | ||
| Key: "json", | ||
| Name: "foo", | ||
| }, | ||
| { | ||
| Key: "hcl", | ||
| Name: "foo", | ||
| }, | ||
| }, | ||
| }, | ||
| { | ||
| name: "tag with multiple keys and different names", | ||
| tag: `json:"foo" hcl:"bar"`, | ||
| want: []*structtag.Tag{ | ||
| { | ||
| Key: "json", | ||
| Name: "foo", | ||
| }, | ||
| { | ||
| Key: "hcl", | ||
| Name: "bar", | ||
| }, | ||
| }, | ||
| }, | ||
| { | ||
| name: "tag with multiple keys, names, and options", | ||
| tag: `json:"foo,omitempty" structs:"bar,omitnested"`, | ||
| want: []*structtag.Tag{ | ||
| { | ||
| Key: "json", | ||
| Name: "foo", | ||
| Options: []string{"omitempty"}, | ||
| }, | ||
| { | ||
| Key: "structs", | ||
| Name: "bar", | ||
| Options: []string{"omitnested"}, | ||
| }, | ||
| }, | ||
| }, | ||
| { | ||
| name: "tag with multiple keys, names, options, and dash", | ||
| tag: `json:"foo" structs:"bar,omitnested" hcl:"-"`, | ||
| want: []*structtag.Tag{ | ||
| { | ||
| Key: "json", | ||
| Name: "foo", | ||
| }, | ||
| { | ||
| Key: "structs", | ||
| Name: "bar", | ||
| Options: []string{"omitnested"}, | ||
| }, | ||
| { | ||
| Key: "hcl", | ||
| Name: "-", | ||
| }, | ||
| }, | ||
| }, | ||
| { | ||
| name: "tag with quoted name", | ||
| tag: `json:"foo,bar:\"baz\""`, | ||
| want: []*structtag.Tag{ | ||
| { | ||
| Key: "json", | ||
| Name: "foo", | ||
| Options: []string{`bar:"baz"`}, | ||
| }, | ||
| }, | ||
| }, | ||
| { | ||
| name: "tag with trailing space", | ||
| tag: `json:"foo" `, | ||
| want: []*structtag.Tag{ | ||
| { | ||
| Key: "json", | ||
| Name: "foo", | ||
| }, | ||
| }, | ||
| }, | ||
| { | ||
| name: "tag with one key (invalid)", | ||
| tag: "json", | ||
| wantErr: errors.New("invalid syntax for struct tag pair"), | ||
| }, | ||
| { | ||
| name: "tag starting with colon (invalid key)", | ||
| tag: ":", | ||
| wantErr: errors.New("invalid syntax for struct tag key"), | ||
| }, | ||
| { | ||
| name: "tag with colon not followed by quote (invalid value)", | ||
| tag: "json:foo", | ||
| wantErr: errors.New("invalid syntax for struct tag value"), | ||
| }, | ||
| { | ||
| name: "tag with unclosed quote (invalid value)", | ||
| tag: `json:"foo`, | ||
| wantErr: errors.New("invalid syntax for struct tag value"), | ||
| }, | ||
| { | ||
| name: "tag with invalid escape sequence (invalid value)", | ||
| tag: `json:"\x"`, | ||
| wantErr: errors.New("invalid syntax for struct tag value"), | ||
| }, | ||
| } | ||
|
|
||
| for _, ts := range test { | ||
| t.Run(ts.name, func(t *testing.T) { | ||
| tags, err := structtag.Parse(ts.tag) | ||
|
|
||
| if ts.wantErr != nil { | ||
| if err == nil || err.Error() != ts.wantErr.Error() { | ||
| t.Errorf("unexpected error: got = %v, want = %v", err, ts.wantErr) | ||
| } | ||
| return | ||
| } | ||
|
|
||
| if ts.want == nil { | ||
| if tags != nil { | ||
| t.Errorf("expected tags to be nil, but got %#v", tags) | ||
| } | ||
| return | ||
| } | ||
|
|
||
| if !reflect.DeepEqual(ts.want, tags) { | ||
| t.Errorf("got = %#v, want = %#v", tags, ts.want) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.