-
Notifications
You must be signed in to change notification settings - Fork 64
Honour disable comments in FIELD_NUMBERS_ORDER_ASCENDING #566
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,6 +5,7 @@ import ( | |
|
|
||
| "github.com/yoheimuta/go-protoparser/v4/parser" | ||
| "github.com/yoheimuta/go-protoparser/v4/parser/meta" | ||
| "github.com/yoheimuta/protolint/linter/disablerule" | ||
| "github.com/yoheimuta/protolint/linter/report" | ||
| "github.com/yoheimuta/protolint/linter/rule" | ||
| "github.com/yoheimuta/protolint/linter/visitor" | ||
|
|
@@ -41,12 +42,14 @@ func (r FieldNumbersOrderAscendingRule) IsOfficial() bool { | |
| func (r FieldNumbersOrderAscendingRule) Apply(proto *parser.Proto) ([]report.Failure, error) { | ||
| v := &fieldNumbersOrderAscendingVisitor{ | ||
| BaseAddVisitor: visitor.NewBaseAddVisitor(r.ID(), string(r.Severity())), | ||
| ruleID: r.ID(), | ||
| } | ||
| return visitor.RunVisitor(v, proto, r.ID()) | ||
| } | ||
|
|
||
| type fieldNumbersOrderAscendingVisitor struct { | ||
| *visitor.BaseAddVisitor | ||
| ruleID string | ||
| } | ||
|
|
||
| // VisitMessage checks the message | ||
|
|
@@ -57,35 +60,48 @@ func (v *fieldNumbersOrderAscendingVisitor) VisitMessage(message *parser.Message | |
| hasError bool | ||
| ) | ||
|
|
||
| // This rule walks the message body itself instead of relying on VisitField, so the | ||
| // disable comments attached to a single field never reach the visitor wrapper and have | ||
| // to be interpreted here. A disabled field keeps taking part in the ordering chain; | ||
| // only the report about it is suppressed. | ||
| // See https://github.com/yoheimuta/protolint/issues/558 | ||
| interpreter := disablerule.NewInterpreter(v.ruleID) | ||
|
|
||
| for _, element := range message.MessageBody { | ||
| field, ok := element.(*parser.Field) | ||
| if !ok { | ||
| continue | ||
| } | ||
|
|
||
| disabled := interpreter.Interpret(field.Comments, field.InlineComment) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Because Useful? React with 👍 / 👎. |
||
|
|
||
| number, err := strconv.Atoi(field.FieldNumber) | ||
| if err != nil { | ||
| v.AddFailuref( | ||
| field.Meta.Pos, | ||
| "field number '%s' is not a number", | ||
| field.FieldNumber, | ||
| ) | ||
|
|
||
| hasError = true | ||
| if !disabled { | ||
| v.AddFailuref( | ||
| field.Meta.Pos, | ||
| "field number '%s' is not a number", | ||
| field.FieldNumber, | ||
| ) | ||
|
|
||
| hasError = true | ||
| } | ||
| continue | ||
| } | ||
|
|
||
| if number <= 0 { | ||
| v.AddFailuref( | ||
| field.Meta.Pos, | ||
| "field number should be positive integer", | ||
| ) | ||
|
|
||
| hasError = true | ||
| if !disabled { | ||
| v.AddFailuref( | ||
| field.Meta.Pos, | ||
| "field number should be positive integer", | ||
| ) | ||
|
|
||
| hasError = true | ||
| } | ||
| continue | ||
| } | ||
|
|
||
| number, isError := v.isAscending(field.Meta.Pos, field.FieldName, number, lastName, lastNumber, false) | ||
| number, isError := v.isAscending(field.Meta.Pos, field.FieldName, number, lastName, lastNumber, false, disabled) | ||
| if isError { | ||
| hasError = true | ||
| } | ||
|
|
@@ -118,35 +134,43 @@ func (v *fieldNumbersOrderAscendingVisitor) VisitEnum(enum *parser.Enum) bool { | |
| } | ||
| } | ||
|
|
||
| interpreter := disablerule.NewInterpreter(v.ruleID) | ||
|
|
||
| for _, element := range enum.EnumBody { | ||
| field, ok := element.(*parser.EnumField) | ||
| if !ok { | ||
| continue | ||
| } | ||
|
|
||
| disabled := interpreter.Interpret(field.Comments, field.InlineComment) | ||
|
|
||
| number, err := strconv.Atoi(field.Number) | ||
| if err != nil { | ||
| v.AddFailuref( | ||
| field.Meta.Pos, | ||
| "field number '%s' is not a number", | ||
| field.Number, | ||
| ) | ||
|
|
||
| hasError = true | ||
| if !disabled { | ||
| v.AddFailuref( | ||
| field.Meta.Pos, | ||
| "field number '%s' is not a number", | ||
| field.Number, | ||
| ) | ||
|
|
||
| hasError = true | ||
| } | ||
| continue | ||
| } | ||
|
|
||
| if number < 0 { | ||
| v.AddFailuref( | ||
| field.Meta.Pos, | ||
| "field number should be positive integer", | ||
| ) | ||
|
|
||
| hasError = true | ||
| if !disabled { | ||
| v.AddFailuref( | ||
| field.Meta.Pos, | ||
| "field number should be positive integer", | ||
| ) | ||
|
|
||
| hasError = true | ||
| } | ||
| continue | ||
| } | ||
|
|
||
| number, isError := v.isAscending(field.Meta.Pos, field.Ident, number, lastIdent, lastNumber, allowAlias) | ||
| number, isError := v.isAscending(field.Meta.Pos, field.Ident, number, lastIdent, lastNumber, allowAlias, disabled) | ||
| if isError { | ||
| hasError = true | ||
| } | ||
|
|
@@ -160,11 +184,15 @@ func (v *fieldNumbersOrderAscendingVisitor) VisitEnum(enum *parser.Enum) bool { | |
|
|
||
| func (v *fieldNumbersOrderAscendingVisitor) isAscending( | ||
| pos meta.Position, fieldName string, number int, lastName string, lastNumber int, allowEqual bool, | ||
| disabled bool, | ||
| ) (curNumber int, hasError bool) { | ||
| if number == lastNumber { | ||
| if allowEqual { | ||
| return number, false | ||
| } | ||
| if disabled { | ||
| return number, false | ||
| } | ||
| v.AddFailuref( | ||
| pos, | ||
| "fields %s and %s have the same number %d", | ||
|
|
@@ -175,6 +203,9 @@ func (v *fieldNumbersOrderAscendingVisitor) isAscending( | |
| } | ||
|
|
||
| if number < lastNumber { | ||
| if disabled { | ||
| return number, false | ||
| } | ||
| v.AddFailuref( | ||
| pos, | ||
| "field %s should be after %s (ascending order expected)", | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,148 @@ | ||
| package rules_test | ||
|
|
||
| import ( | ||
| "reflect" | ||
| "testing" | ||
|
|
||
| "github.com/yoheimuta/go-protoparser/v4/parser" | ||
| "github.com/yoheimuta/go-protoparser/v4/parser/meta" | ||
| "github.com/yoheimuta/protolint/internal/addon/rules" | ||
| "github.com/yoheimuta/protolint/linter/report" | ||
| "github.com/yoheimuta/protolint/linter/rule" | ||
| ) | ||
|
|
||
| // TestFieldNumbersOrderAscendingRule_Apply_disableComments checks that the per-line | ||
| // disable comments are honoured by this rule. | ||
| // See https://github.com/yoheimuta/protolint/issues/558 | ||
| func TestFieldNumbersOrderAscendingRule_Apply_disableComments(t *testing.T) { | ||
| disableThis := func() *parser.Comment { | ||
| return &parser.Comment{Raw: "// protolint:disable:this FIELD_NUMBERS_ORDER_ASCENDING"} | ||
| } | ||
|
|
||
| tests := []struct { | ||
| name string | ||
| inputProto *parser.Proto | ||
| wantFailures []report.Failure | ||
| }{ | ||
| { | ||
| name: "no failures for out-of-order fields disabled by an inline comment", | ||
| inputProto: &parser.Proto{ | ||
| ProtoBody: []parser.Visitee{ | ||
| &parser.Message{ | ||
| MessageBody: []parser.Visitee{ | ||
| &parser.Field{ | ||
| FieldName: "a", | ||
| FieldNumber: "2", | ||
| InlineComment: disableThis(), | ||
| }, | ||
| &parser.Field{ | ||
| FieldName: "b", | ||
| FieldNumber: "1", | ||
| InlineComment: disableThis(), | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| { | ||
| name: "no failures for out-of-order enum fields disabled by an inline comment", | ||
| inputProto: &parser.Proto{ | ||
| ProtoBody: []parser.Visitee{ | ||
| &parser.Enum{ | ||
| EnumBody: []parser.Visitee{ | ||
| &parser.EnumField{ | ||
| Ident: "A", | ||
| Number: "2", | ||
| InlineComment: disableThis(), | ||
| }, | ||
| &parser.EnumField{ | ||
| Ident: "B", | ||
| Number: "1", | ||
| InlineComment: disableThis(), | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| { | ||
| name: "a field following disabled ones is still checked against them", | ||
| inputProto: &parser.Proto{ | ||
| ProtoBody: []parser.Visitee{ | ||
| &parser.Message{ | ||
| MessageBody: []parser.Visitee{ | ||
| &parser.Field{ | ||
| FieldName: "a", | ||
| FieldNumber: "2", | ||
| InlineComment: disableThis(), | ||
| }, | ||
| &parser.Field{ | ||
| FieldName: "b", | ||
| FieldNumber: "1", | ||
| InlineComment: disableThis(), | ||
| }, | ||
| &parser.Field{ | ||
| FieldName: "c", | ||
| FieldNumber: "1", | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| wantFailures: []report.Failure{ | ||
| report.Failuref( | ||
| meta.Position{}, | ||
| "FIELD_NUMBERS_ORDER_ASCENDING", | ||
| string(rule.SeverityError), | ||
| "fields %s and %s have the same number %d", | ||
| "b", "c", 1, | ||
| ), | ||
| }, | ||
| }, | ||
| { | ||
| name: "a field without the comment is still checked", | ||
| inputProto: &parser.Proto{ | ||
| ProtoBody: []parser.Visitee{ | ||
| &parser.Message{ | ||
| MessageBody: []parser.Visitee{ | ||
| &parser.Field{ | ||
| FieldName: "a", | ||
| FieldNumber: "2", | ||
| }, | ||
| &parser.Field{ | ||
| FieldName: "b", | ||
| FieldNumber: "1", | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| wantFailures: []report.Failure{ | ||
| report.Failuref( | ||
| meta.Position{}, | ||
| "FIELD_NUMBERS_ORDER_ASCENDING", | ||
| string(rule.SeverityError), | ||
| "field %s should be after %s (ascending order expected)", | ||
| "a", "b", | ||
| ), | ||
| }, | ||
| }, | ||
| } | ||
|
|
||
| for _, test := range tests { | ||
| t.Run(test.name, func(t *testing.T) { | ||
| r := rules.NewFieldNumbersOrderAscendingRule(rule.SeverityError) | ||
|
|
||
| got, err := r.Apply(test.inputProto) | ||
| if err != nil { | ||
| t.Errorf("got err %v, but want nil", err) | ||
| return | ||
| } | ||
|
|
||
| if !reflect.DeepEqual(got, test.wantFailures) { | ||
| t.Errorf("got %v, but want %v", got, test.wantFailures) | ||
| } | ||
| }) | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When a persistent disable is already active upon entering a message or enum,
extendedDisableRuleVisitorskips this method, so this newly created interpreter never sees anenableattached to a field and checks cannot resume for later fields in that body. Conversely, if this local interpreter encounters a disable after an earlier ordering failure,return !hasErrorprevents the wrapper from traversing the children, so the disable is not carried to later declarations. Both cases violate the documented disable-until-enable-or-EOF behavior; the ordering logic needs to participate in the wrapper's single stateful traversal rather than maintaining an independent interpreter.Useful? React with 👍 / 👎.