...

Source file src/github.com/bazelbuild/buildtools/warn/warn_macro_test.go

Documentation: github.com/bazelbuild/buildtools/warn

     1  /*
     2  Copyright 2020 Google LLC
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      https://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package warn
    18  
    19  import "testing"
    20  
    21  func TestUnnamedMacroNoReaderSameFile(t *testing.T) {
    22  	checkFindings(t, "unnamed-macro", `
    23  load(":foo.bzl", "foo")
    24  
    25  my_rule = rule()
    26  
    27  def macro(x):
    28    foo()
    29    my_rule(name = x)
    30  
    31  def not_macro(x):
    32    foo()
    33    native.glob()
    34    native.existing_rule()
    35    native.existing_rules()
    36    native.package_name()
    37    native.repository_name()
    38    native.exports_files()
    39    return my_rule
    40  
    41  def another_macro(x):
    42    foo()
    43    [native.cc_library() for i in x]
    44  `,
    45  		[]string{
    46  			`5: The macro "macro" should have a keyword argument called "name".
    47  
    48  It is considered a macro because it calls a rule or another macro "my_rule" on line 7.`,
    49  			`19: The macro "another_macro" should have a keyword argument called "name".
    50  
    51  It is considered a macro because it calls a rule or another macro "native.cc_library" on line 21.`,
    52  		},
    53  		scopeBzl)
    54  
    55  	checkFindings(t, "unnamed-macro", `
    56  my_rule = rule()
    57  
    58  def macro1(foo, name, bar):
    59    my_rule()
    60  
    61  def macro2(foo, *, name):
    62    my_rule()
    63  
    64  def macro3(foo, *args, **kwargs):
    65    my_rule()
    66  `,
    67  		[]string{},
    68  		scopeBzl)
    69  
    70  	checkFindings(t, "unnamed-macro", `
    71  my_rule = rule()
    72  
    73  def macro(name):
    74    my_rule(name = name)
    75  
    76  alias = macro
    77  
    78  def bad_macro():
    79    for x in y:
    80      alias(x)
    81  `,
    82  		[]string{
    83  			`8: The macro "bad_macro" should have a keyword argument called "name".
    84  
    85  It is considered a macro because it calls a rule or another macro "alias" on line 10.`,
    86  		},
    87  		scopeBzl)
    88  
    89  	checkFindings(t, "unnamed-macro", `
    90  my_rule = rule()
    91  
    92  def macro1():
    93    my_rule(name = x)
    94  
    95  def macro2():
    96    macro1()
    97  
    98  def macro3():
    99    macro2()
   100  
   101  def macro4():
   102    my_rule()
   103  `,
   104  		[]string{
   105  			`3: The macro "macro1" should have a keyword argument called "name".
   106  
   107  It is considered a macro because it calls a rule or another macro "my_rule" on line 4.`,
   108  			`6: The macro "macro2" should have a keyword argument called "name".
   109  
   110  It is considered a macro because it calls a rule or another macro "macro1" on line 7`,
   111  			`9: The macro "macro3" should have a keyword argument called "name".
   112  
   113  It is considered a macro because it calls a rule or another macro "macro2" on line 10.`,
   114  			`12: The macro "macro4" should have a keyword argument called "name".
   115  
   116  It is considered a macro because it calls a rule or another macro "my_rule" on line 13.`,
   117  		},
   118  		scopeBzl)
   119  }
   120  
   121  func TestUnnamedMacroRecursion(t *testing.T) {
   122  	// Recursion is not allowed in Bazel, but shouldn't cause Buildifier to crash
   123  
   124  	checkFindings(t, "unnamed-macro", `
   125  my_rule = rule()
   126  
   127  def macro():
   128    macro()
   129  `, []string{}, scopeBzl)
   130  
   131  	checkFindings(t, "unnamed-macro", `
   132  my_rule = rule()
   133  
   134  def macro():
   135    macro()
   136  `, []string{}, scopeBzl)
   137  
   138  	checkFindings(t, "unnamed-macro", `
   139  my_rule = rule()
   140  
   141  def macro1():
   142    macro2()
   143  
   144  def macro2():
   145    macro3()
   146  
   147  def macro3():
   148    macro1()
   149  `, []string{}, scopeBzl)
   150  
   151  	checkFindings(t, "unnamed-macro", `
   152  my_rule = rule()
   153  
   154  def foo():
   155    bar()
   156  
   157  def bar():
   158    foo()
   159    my_rule()
   160  `,
   161  		[]string{
   162  			`3: The macro "foo" should have a keyword argument called "name".
   163  
   164  It is considered a macro because it calls a rule or another macro "bar" on line 4.`,
   165  			`6: The macro "bar" should have a keyword argument called "name".
   166  
   167  It is considered a macro because it calls a rule or another macro "my_rule" on line 8.`,
   168  		},
   169  		scopeBzl)
   170  }
   171  
   172  func TestUnnamedMacroWithReader(t *testing.T) {
   173  	defer setUpFileReader(map[string]string{
   174  		"test/package/subdir1/foo.bzl": `
   175  def foo():
   176    native.foo_binary()
   177  
   178  def bar():
   179    foo()
   180  
   181  my_rule = rule()
   182  `,
   183  		"test/package/subdir2/baz.bzl": `
   184  load(":subdir1/foo.bzl", "bar", your_rule = "my_rule")
   185  load("//does/not:exist.bzl", "something")
   186  
   187  def baz():
   188    if False:
   189      bar()
   190  
   191  def qux():
   192    your_rule()
   193  
   194  def f():
   195    something()
   196  `,
   197  	})()
   198  
   199  	checkFindings(t, "unnamed-macro", `
   200  load("//test/package:subdir1/foo.bzl", abc = "bar")
   201  load(":subdir2/baz.bzl", "baz", "qux", "f")
   202  
   203  def macro1(surname):
   204    abc()
   205  
   206  def macro2(surname):
   207    baz()
   208  
   209  def macro3(surname):
   210    qux()
   211  
   212  def not_macro(x):
   213    f()
   214  `,
   215  		[]string{
   216  			`4: The macro "macro1" should have a keyword argument called "name".
   217  
   218  It is considered a macro because it calls a rule or another macro "abc" on line 5.
   219  
   220  By convention, every public macro needs a "name" argument (even if it doesn't use it).
   221  This is important for tooling and automation.
   222  
   223    * If this function is a helper function that's not supposed to be used outside of this file,
   224      please make it private (e.g. rename it to "_macro1").
   225    * Otherwise, add a "name" argument. If possible, use that name when calling other macros/rules.`,
   226  			`7: The macro "macro2" should have a keyword argument called "name".
   227  
   228  It is considered a macro because it calls a rule or another macro "baz" on line 8.
   229  
   230  By convention, every public macro needs a "name" argument (even if it doesn't use it).
   231  This is important for tooling and automation.
   232  
   233    * If this function is a helper function that's not supposed to be used outside of this file,
   234      please make it private (e.g. rename it to "_macro2").
   235    * Otherwise, add a "name" argument. If possible, use that name when calling other macros/rules.`,
   236  			`10: The macro "macro3" should have a keyword argument called "name".
   237  
   238  It is considered a macro because it calls a rule or another macro "qux" on line 11.
   239  
   240  By convention, every public macro needs a "name" argument (even if it doesn't use it).
   241  This is important for tooling and automation.
   242  
   243    * If this function is a helper function that's not supposed to be used outside of this file,
   244      please make it private (e.g. rename it to "_macro3").
   245    * Otherwise, add a "name" argument. If possible, use that name when calling other macros/rules.`,
   246  		},
   247  		scopeBzl)
   248  }
   249  
   250  func TestUnnamedMacroRecursionWithReader(t *testing.T) {
   251  	// Recursion is not allowed in Bazel, but shouldn't cause Buildifier to crash
   252  
   253  	defer setUpFileReader(map[string]string{
   254  		"test/package/foo.bzl": `
   255  load(":bar.bzl", "bar")
   256  
   257  def foo():
   258    foo()
   259  
   260  def baz():
   261    bar()
   262  
   263  def qux():
   264    native.cc_library()
   265  
   266  `,
   267  		"test/package/bar.bzl": `
   268  load(":foo.bzl", "foo", "baz", quuux = "qux")
   269  
   270  def bar():
   271    baz()
   272  
   273  def qux():
   274    foo()
   275    baz()
   276    quuux()
   277  `,
   278  	})()
   279  
   280  	checkFindings(t, "unnamed-macro", `
   281  load(":foo.bzl", "foo", "baz")
   282  load(":bar.bzl", quux = "qux")
   283  
   284  def macro():
   285    foo()
   286    baz()
   287    quux()
   288  `, []string{
   289  		`4: The macro "macro" should have a keyword argument called "name".
   290  
   291  It is considered a macro because it calls a rule or another macro "quux" on line 7.`,
   292  	}, scopeBzl)
   293  }
   294  
   295  func TestUnnamedMacroLoadCycle(t *testing.T) {
   296  	defer setUpFileReader(map[string]string{
   297  		"test/package/foo.bzl": `
   298  load(":test_file.bzl", some_rule = "my_rule")
   299  
   300  def foo():
   301    some_rule()
   302  `,
   303  	})()
   304  
   305  	checkFindings(t, "unnamed-macro", `
   306  load(":foo.bzl", bar = "foo")
   307  
   308  my_rule = rule()
   309  
   310  def macro():
   311    bar()
   312  `, []string{
   313  		`5: The macro "macro" should have a keyword argument called "name".
   314  
   315  It is considered a macro because it calls a rule or another macro "bar" on line 6.`,
   316  	}, scopeBzl)
   317  }
   318  
   319  func TestUnnamedMacroLoadedFiles(t *testing.T) {
   320  	// Test that not necessary files are not loaded
   321  
   322  	defer setUpFileReader(map[string]string{
   323  		"a.bzl": "a = rule()",
   324  		"b.bzl": "b = rule()",
   325  		"c.bzl": "c = rule()",
   326  		"d.bzl": "d = rule()",
   327  	})()
   328  
   329  	checkFindings(t, "unnamed-macro", `
   330  load("//:a.bzl", "a")
   331  load("//:b.bzl", "b")
   332  load("//:c.bzl", "c")
   333  load("//:d.bzl", "d")
   334  
   335  def macro1():
   336    a()  # has to load a.bzl to analyze
   337  
   338  def macro2():
   339    b()  # can skip b.bzl because there's a native rule
   340    native.cc_library()
   341  
   342  def macro3():
   343    c()  # can skip c.bzl because a.bzl has already been loaded
   344    a()
   345  
   346  def macro4():
   347    d()  # can skip d.bzl because there's a rule or another macro defined in the same file
   348    r()
   349  
   350  r = rule()
   351  `, []string{
   352  		`6: The macro "macro1" should have a keyword argument called "name".
   353  
   354  It is considered a macro because it calls a rule or another macro "a" on line 7.`,
   355  		`9: The macro "macro2" should have a keyword argument called "name".
   356  
   357  It is considered a macro because it calls a rule or another macro "native.cc_library" on line 11.`,
   358  		`13: The macro "macro3" should have a keyword argument called "name".
   359  
   360  It is considered a macro because it calls a rule or another macro "a" on line 15.`,
   361  		`17: The macro "macro4" should have a keyword argument called "name".
   362  
   363  It is considered a macro because it calls a rule or another macro "r" on line 19.`,
   364  	}, scopeBzl)
   365  
   366  	if len(fileReaderRequests) == 1 && fileReaderRequests[0] == "a.bzl" {
   367  		return
   368  	}
   369  	t.Errorf("expected to load only a.bzl, instead loaded %d files: %v", len(fileReaderRequests), fileReaderRequests)
   370  }
   371  
   372  func TestUnnamedMacroAliases(t *testing.T) {
   373  	defer setUpFileReader(map[string]string{
   374  		"test/package/foo.bzl": `
   375  load(":bar.bzl", _bar = "bar")
   376  
   377  bar = _bar`,
   378  		"test/package/bar.bzl": `
   379  my_rule = rule()
   380  
   381  bar = my_rule`,
   382  	})()
   383  
   384  	checkFindings(t, "unnamed-macro", `
   385  load(":bar.bzl", "bar")
   386  
   387  baz = bar
   388  
   389  def macro1():
   390    baz()
   391  
   392  def macro2(name):
   393    baz()
   394  `, []string{
   395  		`5: The macro "macro1" should have a keyword argument called "name".
   396  
   397  It is considered a macro because it calls a rule or another macro "baz" on line 6.`,
   398  	}, scopeBzl)
   399  }
   400  
   401  func TestUnnamedMacroPrivate(t *testing.T) {
   402  	checkFindings(t, "unnamed-macro", `
   403  my_rule = rule()
   404  
   405  def _not_macro(x):
   406    my_rule(name = x)
   407  
   408  def macro(x):
   409    _not_macro(x)
   410  `,
   411  		[]string{
   412  			`6: The macro "macro" should have a keyword argument called "name".
   413  
   414  It is considered a macro because it calls a rule or another macro "_not_macro" on line 7.`,
   415  		},
   416  		scopeBzl)
   417  }
   418  
   419  func TestUnnamedMacroTypeAnnotation(t *testing.T) {
   420  	checkFindings(t, "unnamed-macro", `
   421  my_rule = rule()
   422  
   423  def macro(name: string):
   424    my_rule(name)
   425  `,
   426  		[]string{},
   427  		scopeBzl)
   428  
   429  	checkFindings(t, "unnamed-macro", `
   430  my_rule = rule()
   431  
   432  def macro(name: string = "default"):
   433    my_rule(name)
   434  `,
   435  		[]string{},
   436  		scopeBzl)
   437  }
   438  

View as plain text