| // Copyright 2017 The Bazel Authors. All rights reserved. |
| // |
| // 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 |
| // |
| // http://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 com.google.devtools.skylark.skylint; |
| |
| import com.google.common.truth.Truth; |
| import com.google.devtools.build.lib.syntax.BuildFileAST; |
| import java.util.List; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.JUnit4; |
| |
| /** Tests the lint done by {@link NamingConventionsChecker}. */ |
| @RunWith(JUnit4.class) |
| public class UsageCheckerTest { |
| private static List<Issue> findIssues(String... lines) { |
| String content = String.join("\n", lines); |
| BuildFileAST ast = |
| BuildFileAST.parseString( |
| event -> { |
| throw new IllegalArgumentException(event.getMessage()); |
| }, |
| content); |
| return UsageChecker.check(ast); |
| } |
| |
| @Test |
| public void reportUnusedImports() throws Exception { |
| String message = findIssues("load(':foo.bzl', 'x', 'y', _z = 'Z')").toString(); |
| Truth.assertThat(message) |
| .contains( |
| "1:18-1:20: unused binding of 'x'. If you want to re-export a symbol," |
| + " use the following pattern:\n" |
| + "\n" |
| + "load(..., _x = 'x', ...)\n" |
| + "x = _x\n" |
| + "\n" |
| + "More details in the documentation." |
| + " [unused-binding]"); |
| Truth.assertThat(message) |
| .contains( |
| "1:23-1:25: unused binding of 'y'. If you want to re-export a symbol," |
| + " use the following pattern:\n" |
| + "\n" |
| + "load(..., _y = 'y', ...)\n" |
| + "y = _y\n" |
| + "\n" |
| + "More details in the documentation." |
| + " [unused-binding]"); |
| Truth.assertThat(message).contains("1:28-1:29: unused binding of '_z' [unused-binding]"); |
| } |
| |
| @Test |
| public void reportUnusedGlobals() throws Exception { |
| String message = findIssues("_UNUSED = len([])", "def _unused(): pass").toString(); |
| Truth.assertThat(message).contains("1:1-1:7: unused binding of '_UNUSED' [unused-binding]"); |
| Truth.assertThat(message).contains("2:5-2:11: unused binding of '_unused' [unused-binding]"); |
| } |
| |
| @Test |
| public void reportUnusedLocals() throws Exception { |
| String message = findIssues("def some_function(param):", " local, local2 = 1, 3").toString(); |
| Truth.assertThat(message).contains("1:19-1:23: unused binding of 'param'"); |
| Truth.assertThat(message) |
| .contains( |
| "you can add `_ignore = [<param1>, <param2>, ...]` to the function body." |
| + " [unused-binding]"); |
| Truth.assertThat(message).contains("2:3-2:7: unused binding of 'local'"); |
| Truth.assertThat(message) |
| .contains("you can use '_' or rename it to '_local'. [unused-binding]"); |
| Truth.assertThat(message).contains("2:10-2:15: unused binding of 'local2'"); |
| Truth.assertThat(message) |
| .contains("you can use '_' or rename it to '_local2'. [unused-binding]"); |
| } |
| |
| @Test |
| public void reportUnusedComprehensionVariable() throws Exception { |
| String message = findIssues("[[2 for y in []] for x in []]").toString(); |
| Truth.assertThat(message).contains("1:9-1:9: unused binding of 'y'"); |
| Truth.assertThat(message).contains("you can use '_' or rename it to '_y'. [unused-binding]"); |
| Truth.assertThat(message).contains("1:22-1:22: unused binding of 'x'"); |
| Truth.assertThat(message).contains("you can use '_' or rename it to '_x'. [unused-binding]"); |
| } |
| |
| @Test |
| public void reportShadowingVariable() throws Exception { |
| Truth.assertThat( |
| findIssues( |
| "def some_function_name_foo_bar_baz_qux():", |
| " x = [[] for x in []]", |
| " print(x)") |
| .toString()) |
| .contains("2:15-2:15: unused binding of 'x'"); |
| } |
| |
| @Test |
| public void reportShadowedVariable() throws Exception { |
| Truth.assertThat(findIssues("def some_function():", " x = [x for x in []]").toString()) |
| .contains("2:3-2:3: unused binding of 'x'"); |
| } |
| |
| @Test |
| public void reportReassignedUnusedVariable() throws Exception { |
| Truth.assertThat(findIssues("def some_function():", " x = 1", " x += 2").toString()) |
| .contains("3:3-3:3: unused binding of 'x'"); |
| } |
| |
| @Test |
| public void reportUnusedBeforeIfElse() throws Exception { |
| Truth.assertThat( |
| findIssues( |
| "def f(y):", |
| " x = 2", |
| " if y:", |
| " x = 3", |
| " else:", |
| " x = 4", |
| " print(x)") |
| .toString()) |
| .contains("2:3-2:3: unused binding of 'x'"); |
| } |
| |
| @Test |
| public void reportUnusedInForLoop() throws Exception { |
| String messages = |
| findIssues( |
| "def some_function_foo_bar_baz_qux():", |
| " for x in []:", |
| " print(x)", |
| " x = 2") |
| .toString(); |
| Truth.assertThat(messages).contains("4:5-4:5: unused binding of 'x'"); |
| } |
| |
| @Test |
| public void reportUninitializedAfterIfElifElse() throws Exception { |
| String message = |
| findIssues( |
| "def some_function(a, b):", |
| " if a:", |
| " y = 2", |
| " elif b:", |
| " pass", |
| " else:", |
| " y = 1", |
| " y += 2", |
| " print(y)") |
| .toString(); |
| Truth.assertThat(message) |
| .containsMatch( |
| "8:3-8:3: variable 'y' may not have been initialized. .+ \\[uninitialized-variable\\]"); |
| } |
| |
| @Test |
| public void reportUninitializedAfterForLoop() throws Exception { |
| String message = |
| findIssues("def some_function():", " for _ in []:", " y = 1", " print(y)").toString(); |
| Truth.assertThat(message) |
| .containsMatch( |
| "4:9-4:9: variable 'y' may not have been initialized. .+ \\[uninitialized-variable\\]"); |
| } |
| |
| @Test |
| public void dontReportAlwaysInitializedInNestedIf() throws Exception { |
| Truth.assertThat( |
| findIssues( |
| "def some_function(a, b):", |
| " if a:", |
| " if b:", |
| " x = b", |
| " else:", |
| " x = a", |
| " else:", |
| " x = not a", |
| " return x")) |
| .isEmpty(); |
| } |
| |
| @Test |
| public void dontReportAlwaysInitializedBecauseUnreachable() throws Exception { |
| Truth.assertThat( |
| findIssues( |
| "def some_function(a, b):", |
| " if a:", |
| " y = 1", |
| " elif b:", |
| " return", |
| " else:", |
| " fail('fail')", |
| " print(y)", |
| " for _ in []:", |
| " if a:", |
| " break", |
| " elif b:", |
| " continue", |
| " else:", |
| " z = 2", |
| " print(z)")) |
| .isEmpty(); |
| } |
| |
| @Test |
| public void dontReportUsedAsParameterDefault() throws Exception { |
| Truth.assertThat( |
| findIssues( |
| "_x = 1", |
| "def foo(y=_x):", |
| " print(y)", |
| "", |
| "foo()" |
| ) |
| ).isEmpty(); |
| } |
| |
| @Test |
| public void dontReportUsedAfterIf() throws Exception { |
| Truth.assertThat( |
| findIssues( |
| "def some_function(parameter):", |
| " x = 2", |
| " if parameter:", |
| " x = 3", |
| " print(x)")) |
| .isEmpty(); |
| } |
| |
| @Test |
| public void dontReportUsedInNextIteration() throws Exception { |
| Truth.assertThat( |
| findIssues( |
| "def some_function_foo_bar_baz_qux():", |
| " x = 0", |
| " for _ in []:", |
| " print(x)", |
| " x += 1", |
| " return x", |
| "", |
| "def foo():", |
| " x = \"xyz\"", |
| " for i in range(5):", |
| " if i % 2 == 1:", |
| " print(x)", |
| " else:", |
| " x = \"abc\"")) |
| .isEmpty(); |
| } |
| |
| @Test |
| public void dontReportUnusedBuiltins() throws Exception { |
| Truth.assertThat(findIssues()).isEmpty(); |
| } |
| |
| @Test |
| public void dontReportPublicGlobals() throws Exception { |
| Truth.assertThat( |
| findIssues( |
| "GLOBAL = 0", |
| "def global_function_name_foo_bar_baz_qux(parameter):", |
| " print(parameter)")) |
| .isEmpty(); |
| } |
| |
| @Test |
| public void dontReportUsedGlobals() throws Exception { |
| Truth.assertThat( |
| findIssues( |
| "_GLOBAL = 0", |
| "def public_function():", |
| " _global_function(_GLOBAL)", |
| "def _global_function(param):", |
| " print(param)")) |
| .isEmpty(); |
| } |
| |
| @Test |
| public void dontReportUsedLocals() throws Exception { |
| Truth.assertThat( |
| findIssues( |
| "def f(x,y):", |
| " a = x", |
| " b = x", |
| " if x:", |
| " a = y", |
| " if y:", |
| " b = a", |
| " print(b)")) |
| .isEmpty(); |
| } |
| |
| @Test |
| public void dontReportUnderscore() throws Exception { |
| Truth.assertThat(findIssues("GLOBAL = [1 for _ in []]")).isEmpty(); |
| } |
| |
| @Test |
| public void dontReportLocalsStartingWithUnderscore() throws Exception { |
| Truth.assertThat(findIssues("def f(_param):", " _local = [[] for _x in []]")).isEmpty(); |
| Truth.assertThat(findIssues("def f(unused_param):", " unused_local = [[] for unused_x in []]")) |
| .isEmpty(); |
| Truth.assertThat(findIssues("def f():", " UNUSED_CONSTANT = 'unused'")) |
| .isEmpty(); |
| } |
| |
| @Test |
| public void dontReportInitializationWithNoneAsDeclaration() throws Exception { |
| Truth.assertThat( |
| findIssues( |
| "def foo(bar):", |
| " baz = None # here should be no unused warning", |
| " # because we want to allow people to 'declare' a variable in one location", |
| " if bar:", |
| " baz = 0", |
| " else:", |
| " baz = 1", |
| " print(baz)")) |
| .isEmpty(); |
| } |
| |
| @Test |
| public void reportUnusedInitializationWithNone() throws Exception { |
| Truth.assertThat( |
| findIssues("def foo():", " baz = None # warn here because 'baz' is never used") |
| .toString()) |
| .contains("2:3-2:5: unused binding of 'baz'"); |
| } |
| |
| @Test |
| public void reportSubsequentInitializations() throws Exception { |
| Truth.assertThat( |
| findIssues( |
| "def foo():", |
| " baz = None", |
| " baz = None # do warn here (not an initialization)") |
| .toString()) |
| .contains("3:3-3:5: unused binding of 'baz'"); |
| Truth.assertThat( |
| findIssues( |
| "def foo():", |
| " baz = None", |
| " baz = 0 # do warn here (it's a regular assignment)") |
| .toString()) |
| .contains("3:3-3:5: unused binding of 'baz'"); |
| Truth.assertThat( |
| findIssues( |
| "def foo():", |
| " baz = 0", |
| " baz = None # do warn here (not an initialization)") |
| .toString()) |
| .contains("3:3-3:5: unused binding of 'baz'"); |
| } |
| } |