2 * Copyright (C) 2011 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.tools.lint;
19 import com.android.tools.lint.api.DetectorRegistry;
20 import com.android.tools.lint.api.IDomParser;
21 import com.android.tools.lint.api.Lint;
22 import com.android.tools.lint.api.ToolContext;
23 import com.android.tools.lint.checks.BuiltinDetectorRegistry;
24 import com.android.tools.lint.detector.api.Issue;
25 import com.android.tools.lint.detector.api.Location;
26 import com.android.tools.lint.detector.api.Position;
27 import com.android.tools.lint.detector.api.Scope;
28 import com.android.tools.lint.detector.api.Severity;
31 import java.util.ArrayList;
32 import java.util.HashSet;
33 import java.util.List;
37 * Command line driver for the rules framework
41 * <li>Offer priority or category sorting
42 * <li>Offer suppressing violations
45 public class Main implements ToolContext {
46 private static final int ERRNO_ERRORS = -1;
47 private static final int ERRNO_USAGE = -2;
48 private static final int ERRNO_EXISTS = -3;
49 private static final int ERRNO_HELP = -4;
50 private static final int ERRNO_INVALIDARGS = -5;
52 private Set<String> mSuppress = new HashSet<String>();
53 private Set<String> mEnabled = null;
54 private StringBuilder mOutput = new StringBuilder(2000);
55 private boolean mFatal;
56 private String mCommonPrefix;
58 /** Creates a CLI driver */
63 * Runs the static analysis command line driver
65 * @param args program arguments
67 public static void main(String[] args) {
72 * Runs the static analysis command line driver
74 * @param args program arguments
76 private void run(String[] args) {
77 if (args.length < 1) {
79 System.exit(ERRNO_USAGE);
82 DetectorRegistry registry = new BuiltinDetectorRegistry();
84 List<File> files = new ArrayList<File>();
85 for (int index = 0; index < args.length; index++) {
86 String arg = args[index];
87 if (arg.equals("--help") || arg.equals("-h")) { //$NON-NLS-1$ //$NON-NLS-2$
89 System.err.println("\n" +
90 "Run with --suppress <category1[,category2,...]> to suppress categories.");
91 System.exit(ERRNO_HELP);
92 } else if (arg.equals("--suppress")) {
93 if (index == args.length - 1) {
94 System.err.println("Missing categories to suppress");
95 System.exit(ERRNO_INVALIDARGS);
97 String[] ids = args[++index].split(",");
98 for (String id : ids) {
99 if (!registry.isIssueId(id)) {
100 System.err.println("Invalid id \"" + id + "\".");
101 displayValidIds(registry);
102 System.exit(ERRNO_INVALIDARGS);
106 } else if (arg.equals("--enable")) {
107 if (index == args.length - 1) {
108 System.err.println("Missing categories to enable");
109 System.exit(ERRNO_INVALIDARGS);
111 String[] ids = args[++index].split(",");
112 mEnabled = new HashSet<String>();
113 for (String id : ids) {
114 if (!registry.isIssueId(id)) {
115 System.err.println("Invalid id \"" + id + "\".");
116 displayValidIds(registry);
117 System.exit(ERRNO_INVALIDARGS);
122 String filename = arg;
123 File file = new File(filename);
124 if (!file.exists()) {
125 System.err.println(String.format("%1$s does not exist.", filename));
126 System.exit(ERRNO_EXISTS);
130 // TODO: Add flag to point to a file of specific errors to suppress
133 if (files.size() == 0) {
134 System.err.println("No files to analyze.");
135 System.exit(ERRNO_INVALIDARGS);
138 mCommonPrefix = files.get(0).getPath();
139 for (int i = 1; i < files.size(); i++) {
140 File file = files.get(i);
141 String path = file.getPath();
142 mCommonPrefix = getCommonPrefix(mCommonPrefix, path);
145 Lint analyzer = new Lint(new BuiltinDetectorRegistry(), this, Scope.PROJECT);
146 analyzer.analyze(files);
147 if (mOutput.length() == 0) {
148 System.out.println("No warnings.");
149 System.exit(0); // Success error code
151 System.err.println(mOutput.toString());
152 System.err.println("Run with --suppress to turn off specific types of errors, or this message");
154 System.exit(mFatal ? ERRNO_ERRORS : 0);
158 private void displayValidIds(DetectorRegistry registry) {
159 List<Issue> issues = registry.getIssues();
160 System.err.println("Valid issue ids:");
161 for (Issue issue : issues) {
162 System.err.println("\"" + issue.getId() + "\": " + issue.getDescription());
166 private static void printUsage() {
167 // TODO: Look up launcher script name!
168 System.err.println("Usage: lint [--suppress ids] [--enable ids] <project | file> ...");
171 private static String getCommonPrefix(String a, String b) {
172 int aLength = a.length();
173 int bLength = b.length();
174 int aIndex = 0, bIndex = 0;
175 for (; aIndex < aLength && bIndex < bLength; aIndex++, bIndex++) {
176 if (a.charAt(aIndex) != b.charAt(bIndex)) {
181 return a.substring(0, aIndex);
184 public void log(Throwable exception, String format, Object... args) {
185 System.err.println(String.format(format, args));
186 if (exception != null) {
187 exception.printStackTrace();
191 public IDomParser getParser() {
192 return new PositionXmlParser();
195 public boolean isEnabled(Issue issue) {
196 if (mEnabled != null) {
197 return mEnabled.contains(issue.getId());
199 return !mSuppress.contains(issue.getId());
202 public void report(Issue issue, Location location, String message) {
203 if (!isEnabled(issue)) {
207 Severity severity = getSeverity(issue);
208 if (severity == Severity.IGNORE) {
211 if (severity == Severity.ERROR){
215 int startLength = mOutput.length();
217 if (location != null) {
218 File file = location.getFile();
220 String path = file.getPath();
221 if (path.startsWith(mCommonPrefix)) {
222 int chop = mCommonPrefix.length();
223 if (path.length() > chop && path.charAt(chop) == File.separatorChar) {
226 path = path.substring(chop);
228 mOutput.append(path);
232 Position startPosition = location.getStart();
233 if (startPosition != null) {
234 int line = startPosition.getLine();
236 // line is 0-based, should display 1-based
237 mOutput.append(Integer.toString(line + 1));
242 // Column is not particularly useful here
243 //int column = location.getColumn();
245 // mOutput.append(Integer.toString(column));
246 // mOutput.append(':');
249 if (startLength < mOutput.length()) {
254 mOutput.append(severity.getDescription());
258 mOutput.append(message);
260 mOutput.append(' ').append('[');
261 mOutput.append(issue.getId());
265 mOutput.append('\n');
268 public boolean isSuppressed(Issue issue, Location range, String message,
274 public Severity getSeverity(Issue issue) {
275 return issue.getDefaultSeverity();