github.com/yoheimuta/protolint@v0.49.8-0.20240515023657-4ecaebb7575d/internal/addon/rules/indentRule_test.go (about)

     1  package rules_test
     2  
     3  import (
     4  	"reflect"
     5  	"strings"
     6  	"testing"
     7  
     8  	"github.com/yoheimuta/protolint/internal/util_test"
     9  
    10  	"github.com/yoheimuta/go-protoparser/v4/parser/meta"
    11  
    12  	"github.com/yoheimuta/protolint/internal/linter/file"
    13  
    14  	"github.com/yoheimuta/protolint/internal/setting_test"
    15  
    16  	"github.com/yoheimuta/protolint/internal/addon/rules"
    17  	"github.com/yoheimuta/protolint/linter/report"
    18  	"github.com/yoheimuta/protolint/linter/rule"
    19  )
    20  
    21  func TestIndentRule_Apply(t *testing.T) {
    22  	defaultSpace := strings.Repeat(" ", 2)
    23  
    24  	tests := []struct {
    25  		name               string
    26  		inputStyle         string
    27  		inputProtoPath     string
    28  		inputInsertNewline bool
    29  		wantFailures       []report.Failure
    30  		wantExistErr       bool
    31  	}{
    32  		{
    33  			name:           "correct syntax",
    34  			inputProtoPath: setting_test.TestDataPath("rules", "indentrule", "syntax.proto"),
    35  		},
    36  		{
    37  			name:           "incorrect syntax",
    38  			inputStyle:     defaultSpace,
    39  			inputProtoPath: setting_test.TestDataPath("rules", "indentrule", "incorrect_syntax.proto"),
    40  			wantFailures: []report.Failure{
    41  				report.Failuref(
    42  					meta.Position{
    43  						Filename: setting_test.TestDataPath("rules", "indentrule", "incorrect_syntax.proto"),
    44  						Offset:   14,
    45  						Line:     2,
    46  						Column:   5,
    47  					},
    48  					"INDENT",
    49  					`Found an incorrect indentation style "%s". "%s" is correct.`,
    50  					"    ",
    51  					"",
    52  				),
    53  			},
    54  		},
    55  		{
    56  			name:           "correct enum",
    57  			inputProtoPath: setting_test.TestDataPath("rules", "indentrule", "enum.proto"),
    58  		},
    59  		{
    60  			name:           "incorrect enum",
    61  			inputProtoPath: setting_test.TestDataPath("rules", "indentrule", "incorrect_enum.proto"),
    62  			wantFailures: []report.Failure{
    63  				report.Failuref(
    64  					meta.Position{
    65  						Filename: setting_test.TestDataPath("rules", "indentrule", "incorrect_enum.proto"),
    66  						Offset:   67,
    67  						Line:     4,
    68  						Column:   9,
    69  					},
    70  					"INDENT",
    71  					`Found an incorrect indentation style "%s". "%s" is correct.`,
    72  					"        ",
    73  					defaultSpace,
    74  				),
    75  				report.Failuref(
    76  					meta.Position{
    77  						Filename: setting_test.TestDataPath("rules", "indentrule", "incorrect_enum.proto"),
    78  						Offset:   114,
    79  						Line:     6,
    80  						Column:   6,
    81  					},
    82  					"INDENT",
    83  					`Found an incorrect indentation style "%s". "%s" is correct.`,
    84  					"     ",
    85  					defaultSpace,
    86  				),
    87  				report.Failuref(
    88  					meta.Position{
    89  						Filename: setting_test.TestDataPath("rules", "indentrule", "incorrect_enum.proto"),
    90  						Offset:   162,
    91  						Line:     7,
    92  						Column:   2,
    93  					},
    94  					"INDENT",
    95  					`Found an incorrect indentation style "%s". "%s" is correct.`,
    96  					" ",
    97  					"",
    98  				),
    99  			},
   100  		},
   101  		{
   102  			name:           "correct message",
   103  			inputProtoPath: setting_test.TestDataPath("rules", "indentrule", "message.proto"),
   104  		},
   105  		{
   106  			name:           "incorrect message",
   107  			inputProtoPath: setting_test.TestDataPath("rules", "indentrule", "incorrect_message.proto"),
   108  			wantFailures: []report.Failure{
   109  				report.Failuref(
   110  					meta.Position{
   111  						Filename: setting_test.TestDataPath("rules", "indentrule", "incorrect_message.proto"),
   112  						Offset:   100,
   113  						Line:     6,
   114  						Column:   3,
   115  					},
   116  					"INDENT",
   117  					`Found an incorrect indentation style "%s". "%s" is correct.`,
   118  					"  ",
   119  					strings.Repeat(defaultSpace, 2),
   120  				),
   121  				report.Failuref(
   122  					meta.Position{
   123  						Filename: setting_test.TestDataPath("rules", "indentrule", "incorrect_message.proto"),
   124  						Offset:   156,
   125  						Line:     9,
   126  						Column:   1,
   127  					},
   128  					"INDENT",
   129  					`Found an incorrect indentation style "%s". "%s" is correct.`,
   130  					"",
   131  					defaultSpace,
   132  				),
   133  				report.Failuref(
   134  					meta.Position{
   135  						Filename: setting_test.TestDataPath("rules", "indentrule", "incorrect_message.proto"),
   136  						Offset:   287,
   137  						Line:     14,
   138  						Column:   7,
   139  					},
   140  					"INDENT",
   141  					`Found an incorrect indentation style "%s". "%s" is correct.`,
   142  					"      ",
   143  					strings.Repeat(defaultSpace, 2),
   144  				),
   145  			},
   146  		},
   147  		{
   148  			name:           "handle the proto containing extend. Fix https://github.com/yoheimuta/protolint/issues/63",
   149  			inputProtoPath: setting_test.TestDataPath("rules", "indentrule", "issue_63.proto"),
   150  		},
   151  		{
   152  			name: `handle the case that the last rpc method of a service is having a statement block.
   153  Fix https://github.com/yoheimuta/protolint/issues/74`,
   154  			inputProtoPath: setting_test.TestDataPath("rules", "indentrule", "issue_74.proto"),
   155  		},
   156  		{
   157  			name: `skip wrong indentations of inner elements on the same line.
   158  Fix https://github.com/yoheimuta/protolint/issues/139`,
   159  			inputProtoPath: setting_test.TestDataPath("rules", "indentrule", "issue_139.proto"),
   160  		},
   161  		{
   162  			name: `detect only a toplevel indentation mistake and skip other than that on the same line.
   163  Fix https://github.com/yoheimuta/protolint/issues/139`,
   164  			inputProtoPath: setting_test.TestDataPath("rules", "indentrule", "incorrect_issue_139.proto"),
   165  			wantFailures: []report.Failure{
   166  				report.Failuref(
   167  					meta.Position{
   168  						Filename: setting_test.TestDataPath("rules", "indentrule", "incorrect_issue_139.proto"),
   169  						Offset:   222,
   170  						Line:     11,
   171  						Column:   3,
   172  					},
   173  					"INDENT",
   174  					`Found an incorrect indentation style "%s". "%s" is correct.`,
   175  					"  ",
   176  					"",
   177  				),
   178  			},
   179  		},
   180  		{
   181  			name: `do not skip wrong indentations of inner elements on the same line.
   182  Fix https://github.com/yoheimuta/protolint/issues/139`,
   183  			inputProtoPath:     setting_test.TestDataPath("rules", "indentrule", "incorrect_issue_139_short.proto"),
   184  			inputInsertNewline: true,
   185  			wantFailures: []report.Failure{
   186  				report.Failuref(
   187  					meta.Position{
   188  						Filename: setting_test.TestDataPath("rules", "indentrule", "incorrect_issue_139_short.proto"),
   189  						Offset:   82,
   190  						Line:     7,
   191  						Column:   3,
   192  					},
   193  					"INDENT",
   194  					`Found an incorrect indentation style "%s". "%s" is correct.`,
   195  					"  ",
   196  					"",
   197  				),
   198  				report.Failuref(
   199  					meta.Position{
   200  						Filename: setting_test.TestDataPath("rules", "indentrule", "incorrect_issue_139_short.proto"),
   201  						Offset:   104,
   202  						Line:     7,
   203  						Column:   25,
   204  					},
   205  					"INDENT",
   206  					`Found a possible incorrect indentation style. Inserting a new line is recommended.`,
   207  				),
   208  				report.Failuref(
   209  					meta.Position{
   210  						Filename: setting_test.TestDataPath("rules", "indentrule", "incorrect_issue_139_short.proto"),
   211  						Offset:   127,
   212  						Line:     7,
   213  						Column:   48,
   214  					},
   215  					"INDENT",
   216  					`Found a possible incorrect indentation style. Inserting a new line is recommended.`,
   217  				),
   218  			},
   219  		},
   220  		{
   221  			name: `handle the case that the proto has a mixture of line ending formats like LF and CRLF.
   222  Fix https://github.com/yoheimuta/protolint/issues/280`,
   223  			inputProtoPath: setting_test.TestDataPath("rules", "indentrule", "issue_280_mix_lineending.proto"),
   224  			wantFailures: []report.Failure{
   225  				report.Failuref(
   226  					meta.Position{
   227  						Filename: setting_test.TestDataPath("rules", "indentrule", "issue_280_mix_lineending.proto"),
   228  						Offset:   580,
   229  						Line:     27,
   230  						Column:   5,
   231  					},
   232  					"INDENT",
   233  					`Found an incorrect indentation style "%s". "%s" is correct.`,
   234  					"    ",
   235  					"  ",
   236  				),
   237  			},
   238  		},
   239  	}
   240  
   241  	for _, test := range tests {
   242  		test := test
   243  		t.Run(test.name, func(t *testing.T) {
   244  			rule := rules.NewIndentRule(
   245  				rule.SeverityError,
   246  				test.inputStyle,
   247  				!test.inputInsertNewline,
   248  				false,
   249  			)
   250  
   251  			proto, err := file.NewProtoFile(test.inputProtoPath, test.inputProtoPath).Parse(false)
   252  			if err != nil {
   253  				t.Errorf(err.Error())
   254  				return
   255  			}
   256  
   257  			got, err := rule.Apply(proto)
   258  			if test.wantExistErr {
   259  				if err == nil {
   260  					t.Errorf("got err nil, but want err")
   261  				}
   262  				return
   263  			}
   264  			if err != nil {
   265  				t.Errorf("got err %v, but want nil", err)
   266  				return
   267  			}
   268  
   269  			if !reflect.DeepEqual(got, test.wantFailures) {
   270  				t.Errorf("got %v, but want %v", got, test.wantFailures)
   271  				if len(got) != len(test.wantFailures) {
   272  					t.Errorf("len(got) %v, but len(want) %v", len(got), len(test.wantFailures))
   273  					return
   274  				}
   275  				for k, v := range got {
   276  					if !reflect.DeepEqual(v.Pos(), test.wantFailures[k].Pos()) {
   277  						t.Errorf("got[%v].Pos() %v(offset=%v), but want[%v].Pos() %v", k, v.Pos(), v.Pos().Offset, k, test.wantFailures[k].Pos())
   278  						continue
   279  					}
   280  					if !reflect.DeepEqual(v.Message(), test.wantFailures[k].Message()) {
   281  						t.Errorf("got[%v].Message() %v, but want[%v].Message() %v", k, v.Message(), k, test.wantFailures[k].Message())
   282  						continue
   283  					}
   284  					if !reflect.DeepEqual(v, test.wantFailures[k]) {
   285  						t.Errorf("got[%v] %v, but want[%v] %v", k, v, k, test.wantFailures[k])
   286  						continue
   287  					}
   288  				}
   289  			}
   290  		})
   291  	}
   292  }
   293  
   294  func newTestIndentData(
   295  	fileName string,
   296  ) (util_test.TestData, error) {
   297  	return util_test.NewTestData(setting_test.TestDataPath("rules", "indentrule", fileName))
   298  }
   299  
   300  func TestIndentRule_Apply_fix(t *testing.T) {
   301  	space2 := strings.Repeat(" ", 2)
   302  
   303  	correctSyntaxPath, err := newTestIndentData("syntax.proto")
   304  	if err != nil {
   305  		t.Errorf("got err %v", err)
   306  		return
   307  	}
   308  
   309  	incorrectSyntaxPath, err := newTestIndentData("incorrect_syntax.proto")
   310  	if err != nil {
   311  		t.Errorf("got err %v", err)
   312  		return
   313  	}
   314  
   315  	correctEnumPath, err := newTestIndentData("enum.proto")
   316  	if err != nil {
   317  		t.Errorf("got err %v", err)
   318  		return
   319  	}
   320  
   321  	incorrectEnumPath, err := newTestIndentData("incorrect_enum.proto")
   322  	if err != nil {
   323  		t.Errorf("got err %v", err)
   324  		return
   325  	}
   326  
   327  	correctMessagePath, err := newTestIndentData("message.proto")
   328  	if err != nil {
   329  		t.Errorf("got err %v", err)
   330  		return
   331  	}
   332  
   333  	incorrectMessagePath, err := newTestIndentData("incorrect_message.proto")
   334  	if err != nil {
   335  		t.Errorf("got err %v", err)
   336  		return
   337  	}
   338  
   339  	correctIssue99Path, err := newTestIndentData("issue_99.proto")
   340  	if err != nil {
   341  		t.Errorf("got err %v", err)
   342  		return
   343  	}
   344  
   345  	incorrectIssue99Path, err := newTestIndentData("incorrect_issue_99.proto")
   346  	if err != nil {
   347  		t.Errorf("got err %v", err)
   348  		return
   349  	}
   350  
   351  	incorrectIssue139Path, err := newTestIndentData("incorrect_issue_139.proto")
   352  	if err != nil {
   353  		t.Errorf("got err %v", err)
   354  		return
   355  	}
   356  
   357  	correctIssue139Path, err := newTestIndentData("issue_139.proto")
   358  	if err != nil {
   359  		t.Errorf("got err %v", err)
   360  		return
   361  	}
   362  
   363  	correctIssue139InsertPath, err := newTestIndentData("issue_139_insert_linebreaks.proto")
   364  	if err != nil {
   365  		t.Errorf("got err %v", err)
   366  		return
   367  	}
   368  
   369  	tests := []struct {
   370  		name               string
   371  		inputTestData      util_test.TestData
   372  		inputInsertNewline bool
   373  		wantCorrectData    util_test.TestData
   374  	}{
   375  		{
   376  			name:            "correct syntax",
   377  			inputTestData:   correctSyntaxPath,
   378  			wantCorrectData: correctSyntaxPath,
   379  		},
   380  		{
   381  			name:            "incorrect syntax",
   382  			inputTestData:   incorrectSyntaxPath,
   383  			wantCorrectData: correctSyntaxPath,
   384  		},
   385  		{
   386  			name:            "correct enum",
   387  			inputTestData:   correctEnumPath,
   388  			wantCorrectData: correctEnumPath,
   389  		},
   390  		{
   391  			name:            "incorrect enum",
   392  			inputTestData:   incorrectEnumPath,
   393  			wantCorrectData: correctEnumPath,
   394  		},
   395  		{
   396  			name:            "correct message",
   397  			inputTestData:   correctMessagePath,
   398  			wantCorrectData: correctMessagePath,
   399  		},
   400  		{
   401  			name:            "incorrect message",
   402  			inputTestData:   incorrectMessagePath,
   403  			wantCorrectData: correctMessagePath,
   404  		},
   405  		{
   406  			name:            "correct issue_99",
   407  			inputTestData:   correctIssue99Path,
   408  			wantCorrectData: correctIssue99Path,
   409  		},
   410  		{
   411  			name:            "incorrect issue_99",
   412  			inputTestData:   incorrectIssue99Path,
   413  			wantCorrectData: correctIssue99Path,
   414  		},
   415  		{
   416  			name:            "do nothing against inner elements on the same line. Fix https://github.com/yoheimuta/protolint/issues/139",
   417  			inputTestData:   incorrectIssue139Path,
   418  			wantCorrectData: correctIssue139Path,
   419  		},
   420  		{
   421  			name:               "insert linebreaks against inner elements on the same line. Fix https://github.com/yoheimuta/protolint/issues/139",
   422  			inputTestData:      incorrectIssue139Path,
   423  			inputInsertNewline: true,
   424  			wantCorrectData:    correctIssue139InsertPath,
   425  		},
   426  	}
   427  
   428  	for _, test := range tests {
   429  		test := test
   430  		t.Run(test.name, func(t *testing.T) {
   431  			rule_to_test := rules.NewIndentRule(
   432  				rule.SeverityError,
   433  				space2,
   434  				!test.inputInsertNewline,
   435  				true,
   436  			)
   437  
   438  			proto, err := file.NewProtoFile(test.inputTestData.FilePath, test.inputTestData.FilePath).Parse(false)
   439  			if err != nil {
   440  				t.Errorf(err.Error())
   441  				return
   442  			}
   443  
   444  			_, err = rule_to_test.Apply(proto)
   445  			if err != nil {
   446  				t.Errorf("got err %v, but want nil", err)
   447  				return
   448  			}
   449  
   450  			got, err := test.inputTestData.Data()
   451  			if !reflect.DeepEqual(got, test.wantCorrectData.OriginData) {
   452  				t.Errorf(
   453  					"got %s(%v), but want %s(%v)",
   454  					string(got), got,
   455  					string(test.wantCorrectData.OriginData), test.wantCorrectData.OriginData,
   456  				)
   457  			}
   458  
   459  			// restore the file
   460  			defer func() {
   461  				err = test.inputTestData.Restore()
   462  				if err != nil {
   463  					t.Errorf("got err %v", err)
   464  				}
   465  			}()
   466  
   467  			// check whether the modified content can pass the lint in the end.
   468  			ruleOnlyCheck := rules.NewIndentRule(
   469  				rule.SeverityError,
   470  				space2,
   471  				!test.inputInsertNewline,
   472  				false,
   473  			)
   474  			proto, err = file.NewProtoFile(test.inputTestData.FilePath, test.inputTestData.FilePath).Parse(false)
   475  			if err != nil {
   476  				t.Errorf(err.Error())
   477  				return
   478  			}
   479  			gotCheck, err := ruleOnlyCheck.Apply(proto)
   480  			if err != nil {
   481  				t.Errorf("got err %v, but want nil", err)
   482  				return
   483  			}
   484  			if 0 < len(gotCheck) {
   485  				t.Errorf("got failures %v, but want no failures", gotCheck)
   486  				return
   487  			}
   488  		})
   489  	}
   490  }