Add a buildifier warning to enforce symbol load location (#1318)
* Add a buildifier warning to enforce rule load location
* Minor refactoring
* Rename to AllowedSymbolLoadLocations
diff --git a/WARNINGS.md b/WARNINGS.md
index 79b7531..2514b88 100644
--- a/WARNINGS.md
+++ b/WARNINGS.md
@@ -2,6 +2,7 @@
Warning categories supported by buildifier's linter:
+ * [`allowed-symbol-load-locations`](#allowed-symbol-load-locations)
* [`attr-applicable_licenses`](#attr-applicable_licenses)
* [`attr-cfg`](#attr-cfg)
* [`attr-license`](#attr-license)
@@ -128,6 +129,27 @@
--------------------------------------------------------------------------------
+## <a name="allowed-symbol-load-locations"></a>Symbol must be loaded from a specific location
+
+ * Category name: `allowed-symbol-load-locations`
+ * Automatic fix: no
+ * [Suppress the warning](#suppress): `# buildifier: disable=allowed-symbol-load-locations`
+
+Warns when a symbol is loaded from a location other than the expected ones.
+Expected locations are specified in the tables file:
+
+```json
+{
+ "AllowedSymbolLoadLocations": {
+ "genrule": [
+ "//tools/bazel:genrule.bzl"
+ ]
+ }
+}
+```
+
+--------------------------------------------------------------------------------
+
## <a name="attr-applicable_licenses"></a>Do not use `applicable_licenses` as an attribute name.
* Category name: `attr-applicable_licenses`
diff --git a/buildifier/config/config_test.go b/buildifier/config/config_test.go
index 167a21a..c929176 100644
--- a/buildifier/config/config_test.go
+++ b/buildifier/config/config_test.go
@@ -43,6 +43,7 @@
// "mode": "fix",
// "lint": "fix",
// "warningsList": [
+ // "allowed-symbol-load-locations",
// "attr-applicable_licenses",
// "attr-cfg",
// "attr-license",
@@ -263,6 +264,7 @@
"type auto": {options: "--type=auto"},
"type error": {options: "--type=foo", wantErr: fmt.Errorf("unrecognized input type foo; valid types are build, bzl, workspace, default, module, auto")},
"warnings all": {options: "--warnings=all", wantWarnings: []string{
+ "allowed-symbol-load-locations",
"attr-applicable_licenses",
"attr-cfg",
"attr-license",
@@ -364,6 +366,7 @@
"unused-variable",
}},
"warnings default": {options: "--warnings=default", wantWarnings: []string{
+ "allowed-symbol-load-locations",
"attr-applicable_licenses",
"attr-cfg",
"attr-license",
@@ -465,6 +468,7 @@
"unused-variable",
}},
"warnings plus/minus": {options: "--warnings=+unsorted-dict-items,-print,-deprecated-function", wantWarnings: []string{
+ "allowed-symbol-load-locations",
"attr-applicable_licenses",
"attr-cfg",
"attr-license",
@@ -556,7 +560,6 @@
"repository-name",
"return-value",
"rule-impl-return",
-
"skylark-comment",
"skylark-docstring",
"string-iteration",
@@ -567,6 +570,7 @@
"unused-variable",
}},
"warnings no duplicates": {options: "--warnings=+unused-variable", wantWarnings: []string{
+ "allowed-symbol-load-locations",
"attr-applicable_licenses",
"attr-cfg",
"attr-license",
@@ -658,7 +662,6 @@
"repository-name",
"return-value",
"rule-impl-return",
-
"skylark-comment",
"skylark-docstring",
"string-iteration",
diff --git a/buildifier/integration_test.sh b/buildifier/integration_test.sh
index 0b28a4d..b9a6389 100755
--- a/buildifier/integration_test.sh
+++ b/buildifier/integration_test.sh
@@ -258,6 +258,7 @@
"mode": "fix",
"lint": "fix",
"warningsList": [
+ "allowed-symbol-load-locations",
"attr-applicable_licenses",
"attr-cfg",
"attr-license",
@@ -691,3 +692,36 @@
$buildifier --lint=warn --warnings=deprecated-function BUILD 2> report || ret=$?
diff -u report_golden report || die "$1: wrong console output for multifile warnings (WORKSPACE exists)"
+
+cd ..
+
+# Test allowed symbol load locations
+
+mkdir test_dir/allowed_locations
+cd test_dir/allowed_locations
+
+cat > BUILD <<EOF
+load(":f.bzl", "s1", "s2")
+load(":a.bzl", "s3")
+load(":a.bzl", "s4")
+EOF
+
+cat > buildifier.tables <<EOF
+{
+ "AllowedSymbolLoadLocations": {
+ "s1": [":z.bzl"],
+ "s3": [":y.bzl", ":x.bzl"],
+ "s4": [":a.bzl"]
+ }
+}
+EOF
+
+cat > report_golden <<EOF
+BUILD:1: allowed-symbol-load-locations: Symbol "s1" must be loaded from :z.bzl. (https://github.com/bazelbuild/buildtools/blob/main/WARNINGS.md#allowed-symbol-load-locations)
+BUILD:2: allowed-symbol-load-locations: Symbol "s3" must be loaded from one of the allowed locations: :x.bzl, :y.bzl. (https://github.com/bazelbuild/buildtools/blob/main/WARNINGS.md#allowed-symbol-load-locations)
+EOF
+
+$buildifier --lint=warn --warnings=allowed-symbol-load-locations -tables=buildifier.tables BUILD 2> report || true
+diff -u report_golden report || die "$1: wrong console output for allowed symbol load locations"
+
+cd ../..
diff --git a/tables/jsonparser.go b/tables/jsonparser.go
index d2ae1dc..88e40eb 100644
--- a/tables/jsonparser.go
+++ b/tables/jsonparser.go
@@ -31,6 +31,7 @@
NamePriority map[string]int
StripLabelLeadingSlashes bool
ShortenAbsoluteLabelsToRelative bool
+ AllowedSymbolLoadLocations map[string][]string
}
// ParseJSONDefinitions reads and parses JSON table definitions from file.
@@ -55,9 +56,9 @@
}
if merge {
- MergeTables(definitions.IsLabelArg, definitions.LabelDenylist, definitions.IsListArg, definitions.IsSortableListArg, definitions.SortableDenylist, definitions.SortableAllowlist, definitions.NamePriority, definitions.StripLabelLeadingSlashes, definitions.ShortenAbsoluteLabelsToRelative)
+ MergeTables(definitions.IsLabelArg, definitions.LabelDenylist, definitions.IsListArg, definitions.IsSortableListArg, definitions.SortableDenylist, definitions.SortableAllowlist, definitions.NamePriority, definitions.StripLabelLeadingSlashes, definitions.ShortenAbsoluteLabelsToRelative, definitions.AllowedSymbolLoadLocations)
} else {
- OverrideTables(definitions.IsLabelArg, definitions.LabelDenylist, definitions.IsListArg, definitions.IsSortableListArg, definitions.SortableDenylist, definitions.SortableAllowlist, definitions.NamePriority, definitions.StripLabelLeadingSlashes, definitions.ShortenAbsoluteLabelsToRelative)
+ OverrideTables(definitions.IsLabelArg, definitions.LabelDenylist, definitions.IsListArg, definitions.IsSortableListArg, definitions.SortableDenylist, definitions.SortableAllowlist, definitions.NamePriority, definitions.StripLabelLeadingSlashes, definitions.ShortenAbsoluteLabelsToRelative, definitions.AllowedSymbolLoadLocations)
}
return nil
}
diff --git a/tables/jsonparser_test.go b/tables/jsonparser_test.go
index 800934d..e876570 100644
--- a/tables/jsonparser_test.go
+++ b/tables/jsonparser_test.go
@@ -30,13 +30,14 @@
}
expected := Definitions{
- IsLabelArg: map[string]bool{"srcs": true},
- LabelDenylist: map[string]bool{},
- IsSortableListArg: map[string]bool{"srcs": true, "visibility": true},
- SortableDenylist: map[string]bool{"genrule.srcs": true},
- SortableAllowlist: map[string]bool{},
- NamePriority: map[string]int{"name": -1},
- StripLabelLeadingSlashes: true,
+ IsLabelArg: map[string]bool{"srcs": true},
+ LabelDenylist: map[string]bool{},
+ IsSortableListArg: map[string]bool{"srcs": true, "visibility": true},
+ SortableDenylist: map[string]bool{"genrule.srcs": true},
+ SortableAllowlist: map[string]bool{},
+ NamePriority: map[string]int{"name": -1},
+ StripLabelLeadingSlashes: true,
+ AllowedSymbolLoadLocations: map[string][]string{"genrule": {"//tools/bazel:genrule.bzl"}},
}
if !reflect.DeepEqual(expected, definitions) {
t.Errorf("ParseJSONDefinitions(simple_tables.json) = %v; want %v", definitions, expected)
diff --git a/tables/tables.go b/tables/tables.go
index 7ff8b02..49584f4 100644
--- a/tables/tables.go
+++ b/tables/tables.go
@@ -265,8 +265,11 @@
"single_version_override": true,
}
+// AllowedSymbolLoadLocations contains locations for loading rules that are allowed to be used.
+var AllowedSymbolLoadLocations = map[string]map[string]bool{}
+
// OverrideTables allows a user of the build package to override the special-case rules. The user-provided tables replace the built-in tables.
-func OverrideTables(labelArg, denylist, listArg, sortableListArg, sortDenylist, sortAllowlist map[string]bool, namePriority map[string]int, stripLabelLeadingSlashes, shortenAbsoluteLabelsToRelative bool) {
+func OverrideTables(labelArg, denylist, listArg, sortableListArg, sortDenylist, sortAllowlist map[string]bool, namePriority map[string]int, stripLabelLeadingSlashes, shortenAbsoluteLabelsToRelative bool, symbolLoadLocation map[string][]string) {
IsLabelArg = labelArg
LabelDenylist = denylist
IsListArg = listArg
@@ -276,10 +279,19 @@
NamePriority = namePriority
StripLabelLeadingSlashes = stripLabelLeadingSlashes
ShortenAbsoluteLabelsToRelative = shortenAbsoluteLabelsToRelative
+
+ AllowedSymbolLoadLocations = map[string]map[string]bool{}
+ for k, v := range symbolLoadLocation {
+ locations := map[string]bool{}
+ for _, l := range v {
+ locations[l] = true
+ }
+ AllowedSymbolLoadLocations[k] = locations
+ }
}
// MergeTables allows a user of the build package to override the special-case rules. The user-provided tables are merged into the built-in tables.
-func MergeTables(labelArg, denylist, listArg, sortableListArg, sortDenylist, sortAllowlist map[string]bool, namePriority map[string]int, stripLabelLeadingSlashes, shortenAbsoluteLabelsToRelative bool) {
+func MergeTables(labelArg, denylist, listArg, sortableListArg, sortDenylist, sortAllowlist map[string]bool, namePriority map[string]int, stripLabelLeadingSlashes, shortenAbsoluteLabelsToRelative bool, symbolLoadLocation map[string][]string) {
for k, v := range labelArg {
IsLabelArg[k] = v
}
@@ -303,4 +315,12 @@
}
StripLabelLeadingSlashes = stripLabelLeadingSlashes || StripLabelLeadingSlashes
ShortenAbsoluteLabelsToRelative = shortenAbsoluteLabelsToRelative || ShortenAbsoluteLabelsToRelative
+
+ for k, v := range symbolLoadLocation {
+ locations := map[string]bool{}
+ for _, l := range v {
+ locations[l] = true
+ }
+ AllowedSymbolLoadLocations[k] = locations
+ }
}
diff --git a/tables/testdata/simple_tables.json b/tables/testdata/simple_tables.json
index 9243e2d..f28ff4a 100644
--- a/tables/testdata/simple_tables.json
+++ b/tables/testdata/simple_tables.json
@@ -19,5 +19,10 @@
"NamePriority": {
"name": -1
},
- "StripLabelLeadingSlashes": true
+ "StripLabelLeadingSlashes": true,
+ "AllowedSymbolLoadLocations": {
+ "genrule": [
+ "//tools/bazel:genrule.bzl"
+ ]
+ }
}
diff --git a/warn/BUILD.bazel b/warn/BUILD.bazel
index 225b5fc..377a389 100644
--- a/warn/BUILD.bazel
+++ b/warn/BUILD.bazel
@@ -13,6 +13,7 @@
"warn_cosmetic.go",
"warn_deprecated.go",
"warn_docstring.go",
+ "warn_load.go",
"warn_macro.go",
"warn_naming.go",
"warn_operation.go",
@@ -42,6 +43,7 @@
"warn_cosmetic_test.go",
"warn_deprecated_test.go",
"warn_docstring_test.go",
+ "warn_load_test.go",
"warn_macro_test.go",
"warn_naming_test.go",
"warn_operation_test.go",
diff --git a/warn/docs/warnings.textproto b/warn/docs/warnings.textproto
index 9d3eb8e..8be362e 100644
--- a/warn/docs/warnings.textproto
+++ b/warn/docs/warnings.textproto
@@ -3,6 +3,23 @@
# After modifying this file, run `bazel build //warn/docs:warnings_docs && cp bazel-bin/warn/docs/WARNINGS.md .`
warnings: {
+ name: "allowed-symbol-load-locations"
+ header: "Symbol must be loaded from a specific location"
+ description:
+ "Warns when a symbol is loaded from a location other than the expected ones.\n"
+ "Expected locations are specified in the tables file:\n\n"
+ "```json\n"
+ "{\n"
+ " \"AllowedSymbolLoadLocations\": {\n"
+ " \"genrule\": [\n"
+ " \"//tools/bazel:genrule.bzl\"\n"
+ " ]\n"
+ " }\n"
+ "}\n"
+ "```"
+}
+
+warnings: {
name: "attr-cfg"
header: "`cfg = \"data\"` for attr definitions has no effect"
description:
diff --git a/warn/warn.go b/warn/warn.go
index 01bcbb5..2d5d71b 100644
--- a/warn/warn.go
+++ b/warn/warn.go
@@ -116,57 +116,58 @@
// FileWarningMap lists the warnings that run on the whole file.
var FileWarningMap = map[string]func(f *build.File) []*LinterFinding{
- "attr-applicable_licenses": attrApplicableLicensesWarning,
- "attr-cfg": attrConfigurationWarning,
- "attr-license": attrLicenseWarning,
- "attr-licenses": attrLicensesWarning,
- "attr-non-empty": attrNonEmptyWarning,
- "attr-output-default": attrOutputDefaultWarning,
- "attr-single-file": attrSingleFileWarning,
- "build-args-kwargs": argsKwargsInBuildFilesWarning,
- "canonical-repository": canonicalRepositoryWarning,
- "confusing-name": confusingNameWarning,
- "constant-glob": constantGlobWarning,
- "ctx-actions": ctxActionsWarning,
- "ctx-args": contextArgsAPIWarning,
- "depset-items": depsetItemsWarning,
- "depset-iteration": depsetIterationWarning,
- "depset-union": depsetUnionWarning,
- "dict-method-named-arg": dictMethodNamedArgWarning,
- "dict-concatenation": dictionaryConcatenationWarning,
- "duplicated-name": duplicatedNameWarning,
- "external-path": externalPathWarning,
- "filetype": fileTypeWarning,
- "function-docstring": functionDocstringWarning,
- "function-docstring-header": functionDocstringHeaderWarning,
- "function-docstring-args": functionDocstringArgsWarning,
- "function-docstring-return": functionDocstringReturnWarning,
- "integer-division": integerDivisionWarning,
- "keyword-positional-params": keywordPositionalParametersWarning,
- "list-append": listAppendWarning,
- "load": unusedLoadWarning,
- "module-docstring": moduleDocstringWarning,
- "name-conventions": nameConventionsWarning,
- "native-build": nativeInBuildFilesWarning,
- "native-package": nativePackageWarning,
- "no-effect": noEffectWarning,
- "output-group": outputGroupWarning,
- "overly-nested-depset": overlyNestedDepsetWarning,
- "package-name": packageNameWarning,
- "package-on-top": packageOnTopWarning,
- "print": printWarning,
- "provider-params": providerParamsWarning,
- "redefined-variable": redefinedVariableWarning,
- "repository-name": repositoryNameWarning,
- "rule-impl-return": ruleImplReturnWarning,
- "return-value": missingReturnValueWarning,
- "skylark-comment": skylarkCommentWarning,
- "skylark-docstring": skylarkDocstringWarning,
- "string-iteration": stringIterationWarning,
- "uninitialized": uninitializedVariableWarning,
- "unreachable": unreachableStatementWarning,
- "unsorted-dict-items": unsortedDictItemsWarning,
- "unused-variable": unusedVariableWarning,
+ "allowed-symbol-load-locations": symbolLoadLocationWarning,
+ "attr-applicable_licenses": attrApplicableLicensesWarning,
+ "attr-cfg": attrConfigurationWarning,
+ "attr-license": attrLicenseWarning,
+ "attr-licenses": attrLicensesWarning,
+ "attr-non-empty": attrNonEmptyWarning,
+ "attr-output-default": attrOutputDefaultWarning,
+ "attr-single-file": attrSingleFileWarning,
+ "build-args-kwargs": argsKwargsInBuildFilesWarning,
+ "canonical-repository": canonicalRepositoryWarning,
+ "confusing-name": confusingNameWarning,
+ "constant-glob": constantGlobWarning,
+ "ctx-actions": ctxActionsWarning,
+ "ctx-args": contextArgsAPIWarning,
+ "depset-items": depsetItemsWarning,
+ "depset-iteration": depsetIterationWarning,
+ "depset-union": depsetUnionWarning,
+ "dict-method-named-arg": dictMethodNamedArgWarning,
+ "dict-concatenation": dictionaryConcatenationWarning,
+ "duplicated-name": duplicatedNameWarning,
+ "external-path": externalPathWarning,
+ "filetype": fileTypeWarning,
+ "function-docstring": functionDocstringWarning,
+ "function-docstring-header": functionDocstringHeaderWarning,
+ "function-docstring-args": functionDocstringArgsWarning,
+ "function-docstring-return": functionDocstringReturnWarning,
+ "integer-division": integerDivisionWarning,
+ "keyword-positional-params": keywordPositionalParametersWarning,
+ "list-append": listAppendWarning,
+ "load": unusedLoadWarning,
+ "module-docstring": moduleDocstringWarning,
+ "name-conventions": nameConventionsWarning,
+ "native-build": nativeInBuildFilesWarning,
+ "native-package": nativePackageWarning,
+ "no-effect": noEffectWarning,
+ "output-group": outputGroupWarning,
+ "overly-nested-depset": overlyNestedDepsetWarning,
+ "package-name": packageNameWarning,
+ "package-on-top": packageOnTopWarning,
+ "print": printWarning,
+ "provider-params": providerParamsWarning,
+ "redefined-variable": redefinedVariableWarning,
+ "repository-name": repositoryNameWarning,
+ "rule-impl-return": ruleImplReturnWarning,
+ "return-value": missingReturnValueWarning,
+ "skylark-comment": skylarkCommentWarning,
+ "skylark-docstring": skylarkDocstringWarning,
+ "string-iteration": stringIterationWarning,
+ "uninitialized": uninitializedVariableWarning,
+ "unreachable": unreachableStatementWarning,
+ "unsorted-dict-items": unsortedDictItemsWarning,
+ "unused-variable": unusedVariableWarning,
}
// MultiFileWarningMap lists the warnings that run on the whole file, but may use other files.
diff --git a/warn/warn_load.go b/warn/warn_load.go
new file mode 100644
index 0000000..4f9c7fe
--- /dev/null
+++ b/warn/warn_load.go
@@ -0,0 +1,50 @@
+package warn
+
+import (
+ "fmt"
+ "slices"
+ "strings"
+
+ "github.com/bazelbuild/buildtools/build"
+ "github.com/bazelbuild/buildtools/tables"
+)
+
+func symbolLoadLocationWarning(f *build.File) []*LinterFinding {
+ var findings []*LinterFinding
+
+ for stmtIndex := 0; stmtIndex < len(f.Stmt); stmtIndex++ {
+ load, ok := f.Stmt[stmtIndex].(*build.LoadStmt)
+ if !ok {
+ continue
+ }
+
+ for i := 0; i < len(load.From); i++ {
+ from := load.From[i]
+
+ expected, ok := tables.AllowedSymbolLoadLocations[from.Name]
+ if !ok || expected[load.Module.Value] {
+ continue
+ }
+
+ var f *LinterFinding
+ if len(expected) == 1 {
+ var loc string
+ for l := range expected {
+ loc = l
+ break
+ }
+ f = makeLinterFinding(from, fmt.Sprintf("Symbol %q must be loaded from %s.", from.Name, loc))
+ } else {
+ locs := make([]string, 0, len(expected))
+ for l := range expected {
+ locs = append(locs, l)
+ }
+ slices.Sort(locs)
+ f = makeLinterFinding(from, fmt.Sprintf("Symbol %q must be loaded from one of the allowed locations: %s.", from.Name, strings.Join(locs, ", ")))
+ }
+ findings = append(findings, f)
+ }
+
+ }
+ return findings
+}
diff --git a/warn/warn_load_test.go b/warn/warn_load_test.go
new file mode 100644
index 0000000..280271e
--- /dev/null
+++ b/warn/warn_load_test.go
@@ -0,0 +1,43 @@
+/*
+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"
+
+ "github.com/bazelbuild/buildtools/tables"
+)
+
+func TestWarnLoadLocation(t *testing.T) {
+ tables.AllowedSymbolLoadLocations["s1"] = map[string]bool{":z.bzl": true}
+ tables.AllowedSymbolLoadLocations["s3"] = map[string]bool{":x.bzl": true, ":y.bzl": true}
+ tables.AllowedSymbolLoadLocations["s4"] = map[string]bool{":a.bzl": true}
+ checkFindingsAndFix(t, "allowed-symbol-load-locations", `
+load(":f.bzl", "s1", "s2")
+load(":a.bzl", "s3")
+load(":a.bzl", "s4")
+`, `
+load(":f.bzl", "s1", "s2")
+load(":a.bzl", "s3")
+load(":a.bzl", "s4")
+`,
+ []string{
+ ":1: Symbol \"s1\" must be loaded from :z.bzl.",
+ ":2: Symbol \"s3\" must be loaded from one of the allowed locations: :x.bzl, :y.bzl.",
+ },
+ scopeEverywhere)
+}