github.com/yoheimuta/protolint@v0.49.8-0.20240515023657-4ecaebb7575d/linter/visitor/hasExtendedVisitor_test.go (about)

     1  package visitor_test
     2  
     3  import (
     4  	"reflect"
     5  	"testing"
     6  
     7  	"github.com/yoheimuta/go-protoparser/v4/parser/meta"
     8  
     9  	"github.com/yoheimuta/protolint/internal/linter/file"
    10  	"github.com/yoheimuta/protolint/internal/setting_test"
    11  	"github.com/yoheimuta/protolint/internal/util_test"
    12  	"github.com/yoheimuta/protolint/linter/autodisable"
    13  	"github.com/yoheimuta/protolint/linter/visitor"
    14  
    15  	"github.com/yoheimuta/protolint/linter/report"
    16  
    17  	"github.com/yoheimuta/go-protoparser/v4/parser"
    18  )
    19  
    20  type testVisitor struct {
    21  	*visitor.BaseAddVisitor
    22  	next bool
    23  }
    24  
    25  func (v *testVisitor) VisitMessage(message *parser.Message) bool {
    26  	v.AddFailuref(message.Meta.Pos, "Test Message")
    27  	return v.next
    28  }
    29  
    30  type testVisitorInvalidEnumField struct {
    31  	*visitor.BaseAddVisitor
    32  	next bool
    33  }
    34  
    35  func (v *testVisitorInvalidEnumField) VisitEnumField(field *parser.EnumField) bool {
    36  	v.AddFailuref(field.Meta.Pos, "Failed field")
    37  	return v.next
    38  }
    39  
    40  func TestRunVisitor(t *testing.T) {
    41  	tests := []struct {
    42  		name         string
    43  		inputVisitor *testVisitor
    44  		inputProto   *parser.Proto
    45  		inputRuleID  string
    46  		wantExistErr bool
    47  		wantFailures []report.Failure
    48  	}{
    49  		{
    50  			name: "visit no messages",
    51  			inputVisitor: &testVisitor{
    52  				BaseAddVisitor: visitor.NewBaseAddVisitor("MESSAGE_NAMES_UPPER_CAMEL_CASE", "error"),
    53  			},
    54  			inputProto: &parser.Proto{
    55  				Meta: &parser.ProtoMeta{Filename: ""},
    56  			},
    57  		},
    58  		{
    59  			name: "visit a message",
    60  			inputVisitor: &testVisitor{
    61  				BaseAddVisitor: visitor.NewBaseAddVisitor("MESSAGE_NAMES_UPPER_CAMEL_CASE", "error"),
    62  			},
    63  			inputProto: &parser.Proto{
    64  				Meta: &parser.ProtoMeta{Filename: ""},
    65  				ProtoBody: []parser.Visitee{
    66  					&parser.Message{
    67  						Meta: meta.Meta{
    68  							Pos: meta.Position{
    69  								Filename: "example.proto",
    70  								Offset:   100,
    71  								Line:     10,
    72  								Column:   5,
    73  							},
    74  						},
    75  					},
    76  				},
    77  			},
    78  			wantFailures: []report.Failure{
    79  				report.Failuref(
    80  					meta.Position{
    81  						Filename: "example.proto",
    82  						Offset:   100,
    83  						Line:     10,
    84  						Column:   5,
    85  					},
    86  					"MESSAGE_NAMES_UPPER_CAMEL_CASE",
    87  					"Test Message",
    88  				),
    89  			},
    90  		},
    91  		{
    92  			name: "visit messages",
    93  			inputVisitor: &testVisitor{
    94  				BaseAddVisitor: visitor.NewBaseAddVisitor("MESSAGE_NAMES_UPPER_CAMEL_CASE", "error"),
    95  			},
    96  			inputProto: &parser.Proto{
    97  				Meta: &parser.ProtoMeta{Filename: ""},
    98  				ProtoBody: []parser.Visitee{
    99  					&parser.Message{
   100  						Meta: meta.Meta{
   101  							Pos: meta.Position{
   102  								Filename: "example.proto",
   103  								Offset:   100,
   104  								Line:     10,
   105  								Column:   5,
   106  							},
   107  						},
   108  					},
   109  					&parser.Message{
   110  						Meta: meta.Meta{
   111  							Pos: meta.Position{
   112  								Filename: "example.proto",
   113  								Offset:   200,
   114  								Line:     20,
   115  								Column:   10,
   116  							},
   117  						},
   118  					},
   119  				},
   120  			},
   121  			wantFailures: []report.Failure{
   122  				report.Failuref(
   123  					meta.Position{
   124  						Filename: "example.proto",
   125  						Offset:   100,
   126  						Line:     10,
   127  						Column:   5,
   128  					},
   129  					"MESSAGE_NAMES_UPPER_CAMEL_CASE",
   130  					"Test Message",
   131  				),
   132  				report.Failuref(
   133  					meta.Position{
   134  						Filename: "example.proto",
   135  						Offset:   200,
   136  						Line:     20,
   137  						Column:   10,
   138  					},
   139  					"MESSAGE_NAMES_UPPER_CAMEL_CASE",
   140  					"Test Message",
   141  				),
   142  			},
   143  		},
   144  		{
   145  			name: "visit messages recursively",
   146  			inputVisitor: &testVisitor{
   147  				BaseAddVisitor: visitor.NewBaseAddVisitor("MESSAGE_NAMES_UPPER_CAMEL_CASE", "error"),
   148  				next:           true,
   149  			},
   150  			inputProto: &parser.Proto{
   151  				Meta: &parser.ProtoMeta{Filename: ""},
   152  				ProtoBody: []parser.Visitee{
   153  					&parser.Message{
   154  						MessageBody: []parser.Visitee{
   155  							&parser.Message{
   156  								Meta: meta.Meta{
   157  									Pos: meta.Position{
   158  										Filename: "example.proto",
   159  										Offset:   200,
   160  										Line:     20,
   161  										Column:   10,
   162  									},
   163  								},
   164  							},
   165  						},
   166  						Meta: meta.Meta{
   167  							Pos: meta.Position{
   168  								Filename: "example.proto",
   169  								Offset:   100,
   170  								Line:     10,
   171  								Column:   5,
   172  							},
   173  						},
   174  					},
   175  				},
   176  			},
   177  			wantFailures: []report.Failure{
   178  				report.Failuref(
   179  					meta.Position{
   180  						Filename: "example.proto",
   181  						Offset:   100,
   182  						Line:     10,
   183  						Column:   5,
   184  					},
   185  					"MESSAGE_NAMES_UPPER_CAMEL_CASE",
   186  					"Test Message",
   187  				),
   188  				report.Failuref(
   189  					meta.Position{
   190  						Filename: "example.proto",
   191  						Offset:   200,
   192  						Line:     20,
   193  						Column:   10,
   194  					},
   195  					"MESSAGE_NAMES_UPPER_CAMEL_CASE",
   196  					"Test Message",
   197  				),
   198  			},
   199  		},
   200  		{
   201  			name: "visit a message. one is disabled.",
   202  			inputVisitor: &testVisitor{
   203  				BaseAddVisitor: visitor.NewBaseAddVisitor("MESSAGE_NAMES_UPPER_CAMEL_CASE", "error"),
   204  			},
   205  			inputProto: &parser.Proto{
   206  				Meta: &parser.ProtoMeta{Filename: ""},
   207  				ProtoBody: []parser.Visitee{
   208  					&parser.Message{
   209  						Meta: meta.Meta{
   210  							Pos: meta.Position{
   211  								Filename: "example.proto",
   212  								Offset:   100,
   213  								Line:     10,
   214  								Column:   5,
   215  							},
   216  						},
   217  						Comments: []*parser.Comment{
   218  							{
   219  								Raw: `// protolint:disable:next MESSAGE_NAMES_UPPER_CAMEL_CASE`,
   220  							},
   221  						},
   222  					},
   223  					&parser.Message{
   224  						Meta: meta.Meta{
   225  							Pos: meta.Position{
   226  								Filename: "example.proto",
   227  								Offset:   200,
   228  								Line:     20,
   229  								Column:   10,
   230  							},
   231  						},
   232  					},
   233  				},
   234  			},
   235  			inputRuleID: `MESSAGE_NAMES_UPPER_CAMEL_CASE`,
   236  			wantFailures: []report.Failure{
   237  				report.Failuref(
   238  					meta.Position{
   239  						Filename: "example.proto",
   240  						Offset:   200,
   241  						Line:     20,
   242  						Column:   10,
   243  					},
   244  					"MESSAGE_NAMES_UPPER_CAMEL_CASE",
   245  					"Test Message",
   246  				),
   247  			},
   248  		},
   249  		{
   250  			name: "visit a message. one is disabled by an inline comment.",
   251  			inputVisitor: &testVisitor{
   252  				BaseAddVisitor: visitor.NewBaseAddVisitor("MESSAGE_NAMES_UPPER_CAMEL_CASE", "error"),
   253  			},
   254  			inputProto: &parser.Proto{
   255  				Meta: &parser.ProtoMeta{Filename: ""},
   256  				ProtoBody: []parser.Visitee{
   257  					&parser.Message{
   258  						Meta: meta.Meta{
   259  							Pos: meta.Position{
   260  								Filename: "example.proto",
   261  								Offset:   100,
   262  								Line:     10,
   263  								Column:   5,
   264  							},
   265  						},
   266  						InlineComment: &parser.Comment{
   267  							Raw: `// protolint:disable:this MESSAGE_NAMES_UPPER_CAMEL_CASE`,
   268  						},
   269  					},
   270  					&parser.Message{
   271  						Meta: meta.Meta{
   272  							Pos: meta.Position{
   273  								Filename: "example.proto",
   274  								Offset:   200,
   275  								Line:     20,
   276  								Column:   10,
   277  							},
   278  						},
   279  					},
   280  				},
   281  			},
   282  			inputRuleID: `MESSAGE_NAMES_UPPER_CAMEL_CASE`,
   283  			wantFailures: []report.Failure{
   284  				report.Failuref(
   285  					meta.Position{
   286  						Filename: "example.proto",
   287  						Offset:   200,
   288  						Line:     20,
   289  						Column:   10,
   290  					},
   291  					"MESSAGE_NAMES_UPPER_CAMEL_CASE",
   292  					"Test Message",
   293  				),
   294  			},
   295  		},
   296  		{
   297  			name: "visit messages. others are disabled.",
   298  			inputVisitor: &testVisitor{
   299  				BaseAddVisitor: visitor.NewBaseAddVisitor("MESSAGE_NAMES_UPPER_CAMEL_CASE", "error"),
   300  			},
   301  			inputProto: &parser.Proto{
   302  				Meta: &parser.ProtoMeta{Filename: ""},
   303  				ProtoBody: []parser.Visitee{
   304  					&parser.Message{
   305  						Meta: meta.Meta{
   306  							Pos: meta.Position{
   307  								Filename: "example.proto",
   308  								Offset:   100,
   309  								Line:     10,
   310  								Column:   5,
   311  							},
   312  						},
   313  						Comments: []*parser.Comment{
   314  							{
   315  								Raw: `// protolint:disable MESSAGE_NAMES_UPPER_CAMEL_CASE`,
   316  							},
   317  						},
   318  					},
   319  					&parser.Message{
   320  						Meta: meta.Meta{
   321  							Pos: meta.Position{
   322  								Filename: "example.proto",
   323  								Offset:   200,
   324  								Line:     20,
   325  								Column:   10,
   326  							},
   327  						},
   328  					},
   329  					&parser.Message{
   330  						Meta: meta.Meta{
   331  							Pos: meta.Position{
   332  								Filename: "example.proto",
   333  								Offset:   300,
   334  								Line:     30,
   335  								Column:   15,
   336  							},
   337  						},
   338  						Comments: []*parser.Comment{
   339  							{
   340  								Raw: `// protolint:enable MESSAGE_NAMES_UPPER_CAMEL_CASE`,
   341  							},
   342  						},
   343  					},
   344  				},
   345  			},
   346  			inputRuleID: `MESSAGE_NAMES_UPPER_CAMEL_CASE`,
   347  			wantFailures: []report.Failure{
   348  				report.Failuref(
   349  					meta.Position{
   350  						Filename: "example.proto",
   351  						Offset:   300,
   352  						Line:     30,
   353  						Column:   15,
   354  					},
   355  					"MESSAGE_NAMES_UPPER_CAMEL_CASE",
   356  					"Test Message",
   357  				),
   358  			},
   359  		},
   360  		{
   361  			name: "visit messages. others are disabled by a last line comment.",
   362  			inputVisitor: &testVisitor{
   363  				BaseAddVisitor: visitor.NewBaseAddVisitor("MESSAGE_NAMES_UPPER_CAMEL_CASE", "error"),
   364  			},
   365  			inputProto: &parser.Proto{
   366  				Meta: &parser.ProtoMeta{Filename: ""},
   367  				ProtoBody: []parser.Visitee{
   368  					&parser.Message{
   369  						Meta: meta.Meta{
   370  							Pos: meta.Position{
   371  								Filename: "example.proto",
   372  								Offset:   100,
   373  								Line:     10,
   374  								Column:   5,
   375  							},
   376  						},
   377  						Comments: []*parser.Comment{
   378  							{
   379  								Raw: `// protolint:disable MESSAGE_NAMES_UPPER_CAMEL_CASE`,
   380  							},
   381  						},
   382  					},
   383  					&parser.Message{
   384  						Meta: meta.Meta{
   385  							Pos: meta.Position{
   386  								Filename: "example.proto",
   387  								Offset:   200,
   388  								Line:     20,
   389  								Column:   10,
   390  							},
   391  						},
   392  					},
   393  					&parser.Comment{
   394  						Raw: `// protolint:enable MESSAGE_NAMES_UPPER_CAMEL_CASE`,
   395  					},
   396  					&parser.Message{
   397  						Meta: meta.Meta{
   398  							Pos: meta.Position{
   399  								Filename: "example.proto",
   400  								Offset:   300,
   401  								Line:     30,
   402  								Column:   15,
   403  							},
   404  						},
   405  					},
   406  				},
   407  			},
   408  			inputRuleID: `MESSAGE_NAMES_UPPER_CAMEL_CASE`,
   409  			wantFailures: []report.Failure{
   410  				report.Failuref(
   411  					meta.Position{
   412  						Filename: "example.proto",
   413  						Offset:   300,
   414  						Line:     30,
   415  						Column:   15,
   416  					},
   417  					"MESSAGE_NAMES_UPPER_CAMEL_CASE",
   418  					"Test Message",
   419  				),
   420  			},
   421  		},
   422  	}
   423  
   424  	for _, test := range tests {
   425  		test := test
   426  		t.Run(test.name, func(t *testing.T) {
   427  			got, err := visitor.RunVisitor(
   428  				test.inputVisitor,
   429  				test.inputProto,
   430  				test.inputRuleID,
   431  			)
   432  
   433  			if test.wantExistErr {
   434  				if err == nil {
   435  					t.Errorf("got err nil, but want err")
   436  				}
   437  				return
   438  			}
   439  			if err != nil {
   440  				t.Errorf("got err %v, but want nil", err)
   441  				return
   442  			}
   443  
   444  			if !reflect.DeepEqual(got, test.wantFailures) {
   445  				t.Errorf("got %v, but want %v", got, test.wantFailures)
   446  			}
   447  		})
   448  	}
   449  }
   450  
   451  func TestRunVisitorAutoDisable(t *testing.T) {
   452  	tests := []struct {
   453  		name               string
   454  		inputVisitor       visitor.HasExtendedVisitor
   455  		inputFilename      string
   456  		inputRuleID        string
   457  		inputPlacementType autodisable.PlacementType
   458  		wantExistErr       bool
   459  		wantFailureCount   int
   460  		wantFilename       string
   461  	}{
   462  		{
   463  			name: "Do nothing in case of no failures",
   464  			inputVisitor: &testVisitor{
   465  				BaseAddVisitor: visitor.NewBaseAddVisitor("ENUM_FIELD_NAMES_UPPER_SNAKE_CASE", "error"),
   466  			},
   467  			inputFilename:      "invalid.proto",
   468  			inputRuleID:        "ENUM_FIELD_NAMES_UPPER_SNAKE_CASE",
   469  			inputPlacementType: autodisable.Next,
   470  			wantFilename:       "invalid.proto",
   471  		},
   472  		{
   473  			name: "Insert a disable:next comment",
   474  			inputVisitor: &testVisitorInvalidEnumField{
   475  				BaseAddVisitor: visitor.NewBaseAddVisitor("ENUM_FIELD_NAMES_UPPER_SNAKE_CASE", "error"),
   476  			},
   477  			inputFilename:      "invalid.proto",
   478  			inputRuleID:        "ENUM_FIELD_NAMES_UPPER_SNAKE_CASE",
   479  			inputPlacementType: autodisable.Next,
   480  			wantFailureCount:   1,
   481  			wantFilename:       "disable_next.proto",
   482  		},
   483  		{
   484  			name: "Insert a disable:this comment",
   485  			inputVisitor: &testVisitorInvalidEnumField{
   486  				BaseAddVisitor: visitor.NewBaseAddVisitor("ENUM_FIELD_NAMES_UPPER_SNAKE_CASE", "error"),
   487  			},
   488  			inputFilename:      "invalid.proto",
   489  			inputRuleID:        "ENUM_FIELD_NAMES_UPPER_SNAKE_CASE",
   490  			inputPlacementType: autodisable.ThisThenNext,
   491  			wantFailureCount:   1,
   492  			wantFilename:       "disable_this.proto",
   493  		},
   494  	}
   495  
   496  	for _, test := range tests {
   497  		test := test
   498  		t.Run(test.name, func(t *testing.T) {
   499  			input, err := util_test.NewTestData(setting_test.TestDataPath("visitor", test.inputFilename))
   500  			if err != nil {
   501  				t.Errorf("got err %v", err)
   502  				return
   503  			}
   504  
   505  			want, err := util_test.NewTestData(setting_test.TestDataPath("visitor", test.wantFilename))
   506  			if err != nil {
   507  				t.Errorf("got err %v", err)
   508  				return
   509  			}
   510  
   511  			proto, err := file.NewProtoFile(input.FilePath, input.FilePath).Parse(false)
   512  			if err != nil {
   513  				t.Errorf(err.Error())
   514  				return
   515  			}
   516  
   517  			got, err := visitor.RunVisitorAutoDisable(
   518  				test.inputVisitor,
   519  				proto,
   520  				test.inputRuleID,
   521  				test.inputPlacementType,
   522  			)
   523  
   524  			if test.wantExistErr {
   525  				if err == nil {
   526  					t.Errorf("got err nil, but want err")
   527  				}
   528  				return
   529  			} else if err != nil {
   530  				t.Errorf("got err %v, but want nil", err)
   531  				return
   532  			}
   533  
   534  			if len(got) != test.wantFailureCount {
   535  				t.Errorf("len(got) %v, but want %v", len(got), test.wantFailureCount)
   536  			}
   537  
   538  			got2, _ := input.Data()
   539  			if !reflect.DeepEqual(got2, want.OriginData) {
   540  				t.Errorf(
   541  					"got %s(%v), but want %s(%v)",
   542  					string(got2), got,
   543  					string(want.OriginData), want.OriginData,
   544  				)
   545  			}
   546  
   547  			err = input.Restore()
   548  			if err != nil {
   549  				t.Errorf("got err %v", err)
   550  			}
   551  
   552  		})
   553  	}
   554  }