github.com/tiagovtristao/plz@v13.4.0+incompatible/tools/build_langserver/langserver/diagnostics_test.go (about)

     1  package langserver
     2  
     3  import (
     4  	"context"
     5  	"testing"
     6  	"github.com/thought-machine/please/tools/build_langserver/lsp"
     7  
     8  	"github.com/stretchr/testify/assert"
     9  )
    10  
    11  func TestDiagnose(t *testing.T) {
    12  	ds := getDefaultDS(t)
    13  	assert.Equal(t, 13, len(ds.stored))
    14  }
    15  
    16  func TestDiagnosisInvalidBuildLabel(t *testing.T) {
    17  	ds := getDefaultDS(t)
    18  
    19  	// Test for invalid build label
    20  	diag := FindDiagnosticByMsg(ds.stored,
    21  		"Invalid build label //dummy/buildlabels:foo. error: cannot find the path for build label //dummy/buildlabels:foo")
    22  	assert.NotNil(t, diag)
    23  	expected := lsp.Range{
    24  		Start: lsp.Position{Line: 30, Character: 8},
    25  		End:   lsp.Position{Line: 30, Character: 33},
    26  	}
    27  	assert.Equal(t, expected, diag.Range)
    28  
    29  	diag = FindDiagnosticByMsg(ds.stored,
    30  		"Invalid build label //sr. error: cannot find the path for build label //sr:sr")
    31  	assert.NotNil(t, diag)
    32  	expected = lsp.Range{
    33  		Start: lsp.Position{Line: 45, Character: 0},
    34  		End:   lsp.Position{Line: 45, Character: 6},
    35  	}
    36  	assert.Equal(t, expected, diag.Range)
    37  
    38  	// Test for invisible build label
    39  	expected = lsp.Range{
    40  		Start: lsp.Position{Line: 12, Character: 8},
    41  		End:   lsp.Position{Line: 12, Character: 27},
    42  	}
    43  	diag = FindDiagnosticByRange(ds.stored, expected)
    44  	assert.NotNil(t, diag)
    45  	assert.Equal(t, "build label //src/parse/rules is not visible to current package", diag.Message)
    46  
    47  }
    48  
    49  func TestDiagnosisFuncArgument(t *testing.T) {
    50  	ds := getDefaultDS(t)
    51  
    52  	// Test for unexpected argument
    53  	diag := FindDiagnosticByMsg(ds.stored, "unexpected argument foo")
    54  	assert.NotNil(t, diag)
    55  	expected := lsp.Range{
    56  		Start: lsp.Position{Line: 25, Character: 4},
    57  		End:   lsp.Position{Line: 25, Character: 7},
    58  	}
    59  	assert.Equal(t, expected, diag.Range)
    60  
    61  	// Test for invalid argument type
    62  	diag = FindDiagnosticByMsg(ds.stored,
    63  		"invalid type for argument type 'dict' for target, expecting one of [str]")
    64  	assert.NotNil(t, diag)
    65  	expected = lsp.Range{
    66  		Start: lsp.Position{Line: 50, Character: 11},
    67  		End:   lsp.Position{Line: 50, Character: 35},
    68  	}
    69  	assert.Equal(t, expected, diag.Range)
    70  
    71  }
    72  
    73  func TestDiagnosisVariable(t *testing.T) {
    74  	ds := getDefaultDS(t)
    75  
    76  	// Test for undefined variable, variable later defined
    77  	diag := FindDiagnosticByMsg(ds.stored, "unexpected variable or config property 'baz'")
    78  	assert.NotNil(t, diag)
    79  	expected := lsp.Range{
    80  		Start: lsp.Position{Line: 55, Character: 8},
    81  		End:   lsp.Position{Line: 55, Character: 11},
    82  	}
    83  	assert.Equal(t, expected, diag.Range)
    84  
    85  	// Test for undefined variable in function
    86  	diag = FindDiagnosticByMsg(ds.stored, "unexpected variable or config property 'bar'")
    87  	assert.NotNil(t, diag)
    88  	expected = lsp.Range{
    89  		Start: lsp.Position{Line: 51, Character: 4},
    90  		End:   lsp.Position{Line: 51, Character: 7},
    91  	}
    92  	assert.Equal(t, expected, diag.Range)
    93  
    94  }
    95  
    96  func TestDiagnosisFunction(t *testing.T) {
    97  	ds := getDefaultDS(t)
    98  
    99  	// Test for undefined function
   100  	diag := FindDiagnosticByMsg(ds.stored, "function undefined: blah")
   101  	assert.NotNil(t, diag)
   102  	expected := lsp.Range{
   103  		Start: lsp.Position{Line: 53, Character: 0},
   104  		End:   lsp.Position{Line: 53, Character: 4},
   105  	}
   106  	assert.Equal(t, expected, diag.Range)
   107  
   108  	// Test for not enough argument
   109  	diag = FindDiagnosticByMsg(ds.stored, "not enough arguments in call to add_dep")
   110  	assert.NotNil(t, diag)
   111  	expected = lsp.Range{
   112  		Start: lsp.Position{Line: 62, Character: 7},
   113  		End:   lsp.Position{Line: 62, Character: 15},
   114  	}
   115  	assert.Equal(t, expected, diag.Range)
   116  
   117  	for _, d := range ds.stored {
   118  		t.Log(d)
   119  	}
   120  }
   121  
   122  func TestDiagnosticsProperty(t *testing.T) {
   123  	ds := getDefaultDS(t)
   124  
   125  	// Test for invalid string call
   126  	diag := FindDiagnosticByMsg(ds.stored, "function undefined: foo")
   127  	assert.NotNil(t, diag)
   128  	expected := lsp.Range{
   129  		Start: lsp.Position{Line: 64, Character: 30},
   130  		End:   lsp.Position{Line: 64, Character: 33},
   131  	}
   132  	assert.Equal(t, expected, diag.Range)
   133  
   134  	// Test for invalid config property
   135  	diag = FindDiagnosticByMsg(ds.stored, "unexpected variable or config property 'BLAH'")
   136  	assert.NotNil(t, diag)
   137  	expected = lsp.Range{
   138  		Start: lsp.Position{Line: 66, Character: 13},
   139  		End:   lsp.Position{Line: 66, Character: 17},
   140  	}
   141  	assert.Equal(t, expected, diag.Range)
   142  
   143  	// Ensure correct config property is not being listed in diagnostics
   144  	configRange := lsp.Range{
   145  		Start: lsp.Position{Line: 64, Character: 58},
   146  		End:   lsp.Position{Line: 64, Character: 84},
   147  	}
   148  	assert.Nil(t, FindDiagnosticByRange(ds.stored, configRange))
   149  
   150  }
   151  
   152  func TestDiagnoseOutOfScope(t *testing.T) {
   153  	analyzer.State.Config.Parse.BuildFileName = append(analyzer.State.Config.Parse.BuildFileName, "out_of_scope.build")
   154  	ds := &diagnosticStore{
   155  		uri:    OutScopeURI,
   156  		stored: []*lsp.Diagnostic{},
   157  	}
   158  
   159  	stmts, err := analyzer.AspStatementFromFile(OutScopeURI)
   160  	assert.NoError(t, err)
   161  
   162  	ds.storeDiagnostics(analyzer, stmts)
   163  	assert.Equal(t, 1, len(ds.stored))
   164  	assert.NotNil(t, FindDiagnosticByMsg(ds.stored, "unexpected variable or config property 'blah'"))
   165  	for _, d := range ds.stored {
   166  		t.Log(d)
   167  	}
   168  }
   169  
   170  func TestStoreFuncCallDiagnosticsBuildDef(t *testing.T) {
   171  	ctx := context.Background()
   172  	ds := &diagnosticStore{
   173  		uri: exampleBuildURI,
   174  	}
   175  
   176  	buildDefs, err := analyzer.BuildDefsFromURI(ctx, exampleBuildURI)
   177  	assert.NoError(t, err)
   178  
   179  	// Tests for build def
   180  	buildDef := buildDefs["langserver_test"]
   181  	callRange := lsp.Range{
   182  		Start: lsp.Position{Line: 19, Character: 1},
   183  		End:   lsp.Position{Line: 32, Character: 1},
   184  	}
   185  
   186  	ds.diagnoseFuncCall(analyzer, "go_test",
   187  		buildDef.Action.Call.Arguments, callRange)
   188  	expectedRange := lsp.Range{
   189  		Start: lsp.Position{Line: 25, Character: 4},
   190  		End:   lsp.Position{Line: 25, Character: 7},
   191  	}
   192  	diag := FindDiagnosticByMsg(ds.stored,
   193  		"unexpected argument foo")
   194  	assert.Equal(t, expectedRange, diag.Range)
   195  }
   196  
   197  func TestStoreFuncCallDiagnosticsFuncCall(t *testing.T) {
   198  	ds := &diagnosticStore{
   199  		uri:    exampleBuildURI,
   200  		stored: []*lsp.Diagnostic{},
   201  	}
   202  
   203  	stmts, err := analyzer.AspStatementFromFile(exampleBuildURI)
   204  	assert.NoError(t, err)
   205  
   206  	// Test for regular function call with correct argument
   207  	callRange := lsp.Range{
   208  		Start: lsp.Position{Line: 49, Character: 0},
   209  		End:   lsp.Position{Line: 49, Character: 33},
   210  	}
   211  	stmt := analyzer.StatementFromPos(stmts, callRange.Start)
   212  	ds.diagnoseFuncCall(analyzer, "subinclude", stmt.Ident.Action.Call.Arguments, callRange)
   213  	assert.Zero(t, len(ds.stored))
   214  
   215  	// Test for function call with incorrect argument value type
   216  	callRange = lsp.Range{
   217  		Start: lsp.Position{Line: 50, Character: 0},
   218  		End:   lsp.Position{Line: 50, Character: 33},
   219  	}
   220  	stmt = analyzer.StatementFromPos(stmts, callRange.Start)
   221  	ds.diagnoseFuncCall(analyzer, "subinclude", stmt.Ident.Action.Call.Arguments, callRange)
   222  
   223  	expectedRange := lsp.Range{
   224  		Start: lsp.Position{Line: 50, Character: 11},
   225  		End:   lsp.Position{Line: 50, Character: 35},
   226  	}
   227  	diag := FindDiagnosticByMsg(ds.stored,
   228  		"invalid type for argument type 'dict' for target, expecting one of [str]")
   229  	assert.Equal(t, expectedRange, diag.Range)
   230  
   231  }
   232  
   233  func TestDiagnosticFromBuildLabel(t *testing.T) {
   234  	ds := getDefaultDS(t)
   235  
   236  	dummyRange := lsp.Range{
   237  		Start: lsp.Position{Line: 19, Character: 1},
   238  		End:   lsp.Position{Line: 32, Character: 1},
   239  	}
   240  
   241  	// Tests for valid labels
   242  	diag := ds.diagnosticFromBuildLabel(analyzer, "//src/query", dummyRange)
   243  	assert.Nil(t, diag)
   244  	diag = ds.diagnosticFromBuildLabel(analyzer, "//src/query:query", dummyRange)
   245  	assert.Nil(t, diag)
   246  	diag = ds.diagnosticFromBuildLabel(analyzer, ":langserver_test", dummyRange)
   247  	assert.Nil(t, diag)
   248  	diag = ds.diagnosticFromBuildLabel(analyzer, ":langserver", dummyRange)
   249  	assert.Nil(t, diag)
   250  	diag = ds.diagnosticFromBuildLabel(analyzer, "//third_party/go:jsonrpc2", dummyRange)
   251  	assert.Nil(t, diag)
   252  
   253  	// Tests for invalid labels
   254  	diag = ds.diagnosticFromBuildLabel(analyzer, "//src/blah:foo", dummyRange)
   255  	assert.Equal(t, "Invalid build label //src/blah:foo. error: cannot find the path for build label //src/blah:foo", diag.Message)
   256  
   257  	// Tests for invisible labels
   258  	diag = ds.diagnosticFromBuildLabel(analyzer, "//src/output:interactive_display_test", dummyRange)
   259  	assert.Equal(t, "build label //src/output:interactive_display_test is not visible to current package",
   260  		diag.Message)
   261  }
   262  
   263  /************************
   264   * Helper functions
   265   ************************/
   266  func getDefaultDS(t testing.TB) *diagnosticStore {
   267  	if !StringInSlice(analyzer.State.Config.Parse.BuildFileName, "example.build") {
   268  		analyzer.State.Config.Parse.BuildFileName = append(analyzer.State.Config.Parse.BuildFileName, "example.build")
   269  	}
   270  
   271  	ds := &diagnosticStore{
   272  		uri:    exampleBuildURI,
   273  		stored: []*lsp.Diagnostic{},
   274  	}
   275  
   276  	stmts, err := analyzer.AspStatementFromFile(exampleBuildURI)
   277  	assert.NoError(t, err)
   278  
   279  	ds.storeDiagnostics(analyzer, stmts)
   280  
   281  	return ds
   282  }
   283  
   284  func FindDiagnosticByMsg(diag []*lsp.Diagnostic, message string) *lsp.Diagnostic {
   285  	for _, v := range diag {
   286  		if v.Message == message {
   287  			return v
   288  		}
   289  	}
   290  	return nil
   291  }
   292  
   293  func FindDiagnosticByRange(diag []*lsp.Diagnostic, DRange lsp.Range) *lsp.Diagnostic {
   294  	for _, v := range diag {
   295  		if v.Range == DRange {
   296  			return v
   297  		}
   298  	}
   299  	return nil
   300  }