blob: b2e85693124c42f1eefacc48d5d898d7b4556c6b [file] [log] [blame]
/*
Copyright 2020 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package warn
import "testing"
func TestUnnamedMacroNoReaderSameFile(t *testing.T) {
checkFindings(t, "unnamed-macro", `
load(":foo.bzl", "foo")
my_rule = rule()
def a_macro(x):
foo()
my_rule(name = x)
def not_macro(x):
foo()
native.glob()
native.existing_rule()
native.existing_rules()
native.package_name()
native.repository_name()
native.exports_files()
return my_rule
def another_macro(x):
foo()
[native.cc_library() for i in x]
`,
[]string{
`:5: The macro "a_macro" should have a keyword argument called "name".
It is considered a macro as it may produce targets via calls:
test/package:test_file.bzl:7 my_rule
test/package:test_file.bzl:3 rule`,
`:19: The macro "another_macro" should have a keyword argument called "name".
It is considered a macro as it may produce targets via calls:
test/package:test_file.bzl:21 native.cc_library`,
},
scopeBzl)
checkFindings(t, "unnamed-macro", `
my_rule = rule()
def macro1(foo, name, bar):
my_rule()
def macro2(foo, *, name):
my_rule()
def macro3(foo, *args, **kwargs):
my_rule()
`,
[]string{},
scopeBzl)
checkFindings(t, "unnamed-macro", `
my_rule = rule()
def a_macro(name):
my_rule(name = name)
alias = a_macro
def bad_macro():
for x in y:
alias(x)
`,
[]string{
`:8: The macro "bad_macro" should have a keyword argument called "name".
It is considered a macro as it may produce targets via calls:
test/package:test_file.bzl:10 alias
test/package:test_file.bzl:6 a_macro
test/package:test_file.bzl:4 my_rule
test/package:test_file.bzl:1 rule`,
},
scopeBzl)
checkFindings(t, "unnamed-macro", `
my_rule = rule()
def macro1():
my_rule(name = x)
def macro2():
macro1()
def macro3():
macro2()
def macro4():
my_rule()
`,
[]string{
`:3: The macro "macro1" should have a keyword argument called "name".
It is considered a macro as it may produce targets via calls:
test/package:test_file.bzl:4 my_rule
test/package:test_file.bzl:1 rule`,
`:6: The macro "macro2" should have a keyword argument called "name".
It is considered a macro as it may produce targets via calls:
test/package:test_file.bzl:7 macro1
test/package:test_file.bzl:4 my_rule
test/package:test_file.bzl:1 rule`,
`:9: The macro "macro3" should have a keyword argument called "name".
It is considered a macro as it may produce targets via calls:
test/package:test_file.bzl:10 macro2
test/package:test_file.bzl:7 macro1
test/package:test_file.bzl:4 my_rule
test/package:test_file.bzl:1 rule`,
`:12: The macro "macro4" should have a keyword argument called "name".
It is considered a macro as it may produce targets via calls:
test/package:test_file.bzl:13 my_rule
test/package:test_file.bzl:1 rule`,
},
scopeBzl)
}
func TestUnnamedMacroRecursion(t *testing.T) {
// Recursion is not allowed in Bazel, but shouldn't cause Buildifier to crash
checkFindings(t, "unnamed-macro", `
my_rule = rule()
def a_macro():
a_macro()
`, []string{}, scopeBzl)
checkFindings(t, "unnamed-macro", `
my_rule = rule()
def a_macro():
a_macro()
`, []string{}, scopeBzl)
checkFindings(t, "unnamed-macro", `
my_rule = rule()
def macro1():
macro2()
def macro2():
macro3()
def macro3():
macro1()
`, []string{}, scopeBzl)
checkFindings(t, "unnamed-macro", `
my_rule = rule()
def foo():
bar()
def bar():
foo()
my_rule()
`,
[]string{
`:3: The macro "foo" should have a keyword argument called "name".
It is considered a macro as it may produce targets via calls:
test/package:test_file.bzl:4 bar
test/package:test_file.bzl:8 my_rule
test/package:test_file.bzl:1 rule`,
`:6: The macro "bar" should have a keyword argument called "name".
It is considered a macro as it may produce targets via calls:
test/package:test_file.bzl:8 my_rule
test/package:test_file.bzl:1 rule`,
},
scopeBzl)
}
func TestUnnamedMacroWithReader(t *testing.T) {
defer setUpFileReader(map[string]string{
"test/package/subdir1/foo.bzl": `
def foo():
native.foo_binary()
def bar():
foo()
my_rule = rule()
`,
"test/package/subdir2/baz.bzl": `
load(":subdir1/foo.bzl", "bar", your_rule = "my_rule")
load("//does/not:exist.bzl", "something")
def baz():
if False:
bar()
def qux():
your_rule()
def f():
something()
`,
})()
checkFindings(t, "unnamed-macro", `
load("//test/package:subdir1/foo.bzl", abc = "bar")
load(":subdir2/baz.bzl", "baz", "qux", "f")
def macro1(surname):
abc()
def macro2(surname):
baz()
def macro3(surname):
qux()
def not_macro(x):
f()
`,
[]string{
`:4: The macro "macro1" should have a keyword argument called "name".
It is considered a macro as it may produce targets via calls:
test/package:test_file.bzl:5 abc
test/package:subdir1/foo.bzl:1 bar
test/package:subdir1/foo.bzl:6 foo
test/package:subdir1/foo.bzl:3 native.foo_binary
By convention, every public macro needs a "name" argument (even if it doesn't use it).
This is important for tooling and automation.
* If this function is a helper function that's not supposed to be used outside of this file,
please make it private (e.g. rename it to "_macro1").
* Otherwise, add a "name" argument. If possible, use that name when calling other macros/rules.`,
`:7: The macro "macro2" should have a keyword argument called "name".
It is considered a macro as it may produce targets via calls:
test/package:test_file.bzl:8 baz
test/package:subdir2/baz.bzl:2 baz
test/package:subdir2/baz.bzl:7 bar
test/package:subdir1/foo.bzl:2 bar
test/package:subdir1/foo.bzl:6 foo
test/package:subdir1/foo.bzl:3 native.foo_binary
By convention, every public macro needs a "name" argument (even if it doesn't use it).
This is important for tooling and automation.
* If this function is a helper function that's not supposed to be used outside of this file,
please make it private (e.g. rename it to "_macro2").
* Otherwise, add a "name" argument. If possible, use that name when calling other macros/rules.`,
`:10: The macro "macro3" should have a keyword argument called "name".
It is considered a macro as it may produce targets via calls:
test/package:test_file.bzl:11 qux
test/package:subdir2/baz.bzl:2 qux
test/package:subdir2/baz.bzl:10 your_rule
test/package:subdir1/foo.bzl:2 my_rule
test/package:subdir1/foo.bzl:8 rule
By convention, every public macro needs a "name" argument (even if it doesn't use it).
This is important for tooling and automation.
* If this function is a helper function that's not supposed to be used outside of this file,
please make it private (e.g. rename it to "_macro3").
* Otherwise, add a "name" argument. If possible, use that name when calling other macros/rules.`,
},
scopeBzl)
}
func TestUnnamedMacroRecursionWithReader(t *testing.T) {
// Recursion is not allowed in Bazel, but shouldn't cause Buildifier to crash
defer setUpFileReader(map[string]string{
"test/package/foo.bzl": `
load(":bar.bzl", "bar")
def foo():
foo()
def baz():
bar()
def qux():
native.cc_library()
`,
"test/package/bar.bzl": `
load(":foo.bzl", "foo", "baz", quuux = "qux")
def bar():
baz()
def qux():
foo()
baz()
quuux()
`,
})()
checkFindings(t, "unnamed-macro", `
load(":foo.bzl", "foo", "baz")
load(":bar.bzl", quux = "qux")
def a_macro():
foo()
baz()
quux()
`, []string{
`:4: The macro "a_macro" should have a keyword argument called "name".
It is considered a macro as it may produce targets via calls:
test/package:test_file.bzl:7 quux
test/package:bar.bzl:2 qux
test/package:bar.bzl:10 quuux
test/package:foo.bzl:2 qux
test/package:foo.bzl:11 native.cc_library`,
}, scopeBzl)
}
func TestUnnamedMacroLoadCycle(t *testing.T) {
defer setUpFileReader(map[string]string{
"test/package/foo.bzl": `
load(":test_file.bzl", some_rule = "my_rule")
def foo():
some_rule()
`,
})()
checkFindings(t, "unnamed-macro", `
load(":foo.bzl", bar = "foo")
my_rule = rule()
def a_macro():
bar()
`, []string{
`:5: The macro "a_macro" should have a keyword argument called "name".
It is considered a macro as it may produce targets via calls:
test/package:test_file.bzl:6 bar
test/package:foo.bzl:1 foo
test/package:foo.bzl:5 some_rule
test/package:test_file.bzl:2 my_rule
test/package:test_file.bzl:3 rule`,
}, scopeBzl)
}
func TestUnnamedMacroLoadedFiles(t *testing.T) {
// Test that not necessary files are not loaded
defer setUpFileReader(map[string]string{
"a.bzl": "a = rule()",
"b.bzl": "b = rule()",
"c.bzl": "c = rule()",
"d.bzl": "d = rule()",
})()
checkFindings(t, "unnamed-macro", `
load("//:a.bzl", "a")
load("//:b.bzl", "b")
load("//:c.bzl", "c")
load("//:d.bzl", "d")
def macro1():
a() # has to load a.bzl to analyze
def macro2():
b() # can skip b.bzl because there's a native rule
native.cc_library()
def macro3():
c() # can skip c.bzl because a.bzl has already been loaded
a()
def macro4():
d() # can skip d.bzl because there's a rule or another macro defined in the same file
r()
r = rule()
`, []string{
`:6: The macro "macro1" should have a keyword argument called "name".
It is considered a macro as it may produce targets via calls:
test/package:test_file.bzl:7 a
:a.bzl:1 a
:a.bzl:1 rule`,
`:9: The macro "macro2" should have a keyword argument called "name".
It is considered a macro as it may produce targets via calls:
test/package:test_file.bzl:11 native.cc_library`,
`:13: The macro "macro3" should have a keyword argument called "name".
It is considered a macro as it may produce targets via calls:
test/package:test_file.bzl:15 a
:a.bzl:1 a
:a.bzl:1 rule`,
`:17: The macro "macro4" should have a keyword argument called "name".
It is considered a macro as it may produce targets via calls:
test/package:test_file.bzl:19 r
test/package:test_file.bzl:21 rule`,
}, scopeBzl)
if len(fileReaderRequests) == 1 && fileReaderRequests[0] == "a.bzl" {
return
}
t.Errorf("expected to load only a.bzl, instead loaded %d files: %v", len(fileReaderRequests), fileReaderRequests)
}
func TestUnnamedMacroAliases(t *testing.T) {
defer setUpFileReader(map[string]string{
"test/package/foo.bzl": `
load(":bar.bzl", _bar = "bar")
bar = _bar`,
"test/package/bar.bzl": `
my_rule = rule()
bar = my_rule`,
})()
checkFindings(t, "unnamed-macro", `
load(":bar.bzl", "bar")
baz = bar
def macro1():
baz()
def macro2(name):
baz()
`, []string{
`:5: The macro "macro1" should have a keyword argument called "name".
It is considered a macro as it may produce targets via calls:
test/package:test_file.bzl:6 baz
test/package:test_file.bzl:3 bar
test/package:bar.bzl:1 bar
test/package:bar.bzl:4 my_rule
test/package:bar.bzl:2 rule`,
}, scopeBzl)
}
func TestUnnamedMacroPrivate(t *testing.T) {
checkFindings(t, "unnamed-macro", `
my_rule = rule()
def _not_macro(x):
my_rule(name = x)
def a_macro(x):
_not_macro(x)
`,
[]string{
`:6: The macro "a_macro" should have a keyword argument called "name".
It is considered a macro as it may produce targets via calls:
test/package:test_file.bzl:7 _not_macro
test/package:test_file.bzl:4 my_rule
test/package:test_file.bzl:1 rule`,
},
scopeBzl)
}
func TestUnnamedMacroTypeAnnotation(t *testing.T) {
checkFindings(t, "unnamed-macro", `
my_rule = rule()
def a_macro(name: string):
my_rule(name)
`,
[]string{},
scopeBzl)
checkFindings(t, "unnamed-macro", `
my_rule = rule()
def a_macro(name: string = "default"):
my_rule(name)
`,
[]string{},
scopeBzl)
}