aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/tools/skylark/java/com/google/devtools/skylark/skylint/NativeRecursiveGlobChecker.java
blob: 9857d6655bb1fb6641490f515d809af60b4c7cd8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
// 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.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.devtools.build.lib.syntax.Argument;
import com.google.devtools.build.lib.syntax.AssignmentStatement;
import com.google.devtools.build.lib.syntax.BuildFileAST;
import com.google.devtools.build.lib.syntax.Expression;
import com.google.devtools.build.lib.syntax.FuncallExpression;
import com.google.devtools.build.lib.syntax.Identifier;
import com.google.devtools.build.lib.syntax.ListLiteral;
import com.google.devtools.build.lib.syntax.StringLiteral;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Checks the adherence to Skylark best practices for recursive globs.
 *
 * <p>Recursive globs should be used sparingly and not for files containing source code. This check
 * flags incorrect usage of recurisive globs, for languages known to be problematic.
 */
public class NativeRecursiveGlobChecker extends AstVisitorWithNameResolution {

  /* List of instances of glob(**) we found. */
  private final List<Issue> issues = new ArrayList<>();

  /* List of known variables we've encountered for finding indirect use of glob(**) */
  private final Map<Identifier, Expression> vars = new HashMap<>();

  /* List of variables that were found in globs, but had not yet been resolved in SkyLark
   * processing.  Example:
   *
   * native.glob([my_var])
   *
   * ...
   *
   * my_var = "**\/*.java"
   */
  private final Set<Identifier> waitingFor = new HashSet<>();

  private static final String BAD_RECURSIVE_GLOB = "bad-recursive-glob";

  public static List<Issue> check(BuildFileAST ast) {
    NativeRecursiveGlobChecker checker = new NativeRecursiveGlobChecker();
    checker.visit(ast);
    return checker.issues;
  }

  private void evaluateInclude(Expression exp) {
    if (exp.kind() == Expression.Kind.STRING_LITERAL) {
      StringLiteral str = (StringLiteral) exp;
      String value = str.getValue();
      if (value.contains("**") && value.endsWith("*.java")) {
        issues.add(
            Issue.create(
                BAD_RECURSIVE_GLOB,
                "go/build-style#globs "
                    + "Do not use recursive globs for Java source files. glob() on multiple "
                    + "directories is error prone and can cause serious maintenance problems for "
                    + "BUILD files.", exp.getLocation()));
      }
    } else if (exp.kind() == Expression.Kind.IDENTIFIER) {
      Identifier id = (Identifier) exp;
      if (vars.containsKey(id)) {
        evaluateInclude(vars.get(id));
      } else {
        waitingFor.add(id);
      }
    }
  }

  @Override
  public void visit(FuncallExpression node) {
    if (node.getFunction().toString().equals("glob")) {
      Argument.Passed include = null;
      int index = 0;
      List<Argument.Passed> args = node.getArguments();
      for (Argument.Passed a : args) {
        if (index == 0 && a.isPositional()) {
          include = a;
          break;
        } else if (index > 1
            && a.isKeyword()
            && (a.getName() != null && a.getName().equals("include"))) {
          include = a;
          break;
        }
        index++;
      }
      if (include != null && include.getValue().kind() == Expression.Kind.LIST_LITERAL) {
        ListLiteral list = (ListLiteral) include.getValue();
        for (Expression exp : list.getElements()) {
          evaluateInclude(exp);
        }
      }
    }
    super.visit(node);
  }

  @Override
  public void visit(AssignmentStatement node) {
    super.visit(node);
    ImmutableSet<Identifier> lvalues = node.getLValue().boundIdentifiers();
    if (lvalues.size() != 1) {
      return;
    }
    Identifier ident = Iterables.getOnlyElement(lvalues);
    vars.put(ident, node.getExpression());

    if (waitingFor.contains(ident)) {
      evaluateInclude(node.getExpression());
    }
  }
}