OSDN Git Service

More lint checks: translation, i18n, proguard, gridlayout, "px"
[android-x86/sdk.git] / lint / cli / src / com / android / tools / lint / Main.java
1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
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
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 package com.android.tools.lint;
18
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;
29
30 import java.io.File;
31 import java.util.ArrayList;
32 import java.util.HashSet;
33 import java.util.List;
34 import java.util.Set;
35
36 /**
37  * Command line driver for the rules framework
38  * <p>
39  * TODO:
40  * <ul>
41  * <li>Offer priority or category sorting
42  * <li>Offer suppressing violations
43  * </ul>
44  */
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;
51
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;
57
58     /** Creates a CLI driver */
59     public Main() {
60     }
61
62     /**
63      * Runs the static analysis command line driver
64      *
65      * @param args program arguments
66      */
67     public static void main(String[] args) {
68         new Main().run(args);
69     }
70
71     /**
72      * Runs the static analysis command line driver
73      *
74      * @param args program arguments
75      */
76     private void run(String[] args) {
77         if (args.length < 1) {
78             printUsage();
79             System.exit(ERRNO_USAGE);
80         }
81
82         DetectorRegistry registry = new BuiltinDetectorRegistry();
83
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$
88                 printUsage();
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);
96                 }
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);
103                     }
104                     mSuppress.add(id);
105                 }
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);
110                 }
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);
118                     }
119                     mEnabled.add(id);
120                 }
121             } else {
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);
127                 }
128                 files.add(file);
129             }
130             // TODO: Add flag to point to a file of specific errors to suppress
131         }
132
133         if (files.size() == 0) {
134             System.err.println("No files to analyze.");
135             System.exit(ERRNO_INVALIDARGS);
136         }
137
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);
143         }
144
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
150         } else {
151             System.err.println(mOutput.toString());
152             System.err.println("Run with --suppress to turn off specific types of errors, or this message");
153
154             System.exit(mFatal ? ERRNO_ERRORS : 0);
155         }
156     }
157
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());
163         }
164     }
165
166     private static void printUsage() {
167         // TODO: Look up launcher script name!
168         System.err.println("Usage: lint [--suppress ids] [--enable ids] <project | file> ...");
169     }
170
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)) {
177                 break;
178             }
179         }
180
181         return a.substring(0, aIndex);
182     }
183
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();
188         }
189     }
190
191     public IDomParser getParser() {
192         return new PositionXmlParser();
193     }
194
195     public boolean isEnabled(Issue issue) {
196         if (mEnabled != null) {
197             return mEnabled.contains(issue.getId());
198         }
199         return !mSuppress.contains(issue.getId());
200     }
201
202     public void report(Issue issue, Location location, String message) {
203         if (!isEnabled(issue)) {
204             return;
205         }
206
207         Severity severity = getSeverity(issue);
208         if (severity == Severity.IGNORE) {
209             return;
210         }
211         if (severity == Severity.ERROR){
212             mFatal = true;
213         }
214
215         int startLength = mOutput.length();
216
217         if (location != null) {
218             File file = location.getFile();
219             if (file != null) {
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) {
224                         chop++;
225                     }
226                     path = path.substring(chop);
227                 }
228                 mOutput.append(path);
229                 mOutput.append(':');
230             }
231
232             Position startPosition = location.getStart();
233             if (startPosition != null) {
234                 int line = startPosition.getLine();
235                 if (line >= 0) {
236                     // line is 0-based, should display 1-based
237                     mOutput.append(Integer.toString(line + 1));
238                     mOutput.append(':');
239                 }
240             }
241
242             // Column is not particularly useful here
243             //int column = location.getColumn();
244             //if (column > 0) {
245             //    mOutput.append(Integer.toString(column));
246             //    mOutput.append(':');
247             //}
248
249             if (startLength < mOutput.length()) {
250                 mOutput.append(' ');
251             }
252         }
253
254         mOutput.append(severity.getDescription());
255         mOutput.append(':');
256         mOutput.append(' ');
257
258         mOutput.append(message);
259         if (issue != null) {
260             mOutput.append(' ').append('[');
261             mOutput.append(issue.getId());
262             mOutput.append(']');
263         }
264
265         mOutput.append('\n');
266     }
267
268     public boolean isSuppressed(Issue issue, Location range, String message,
269             Severity severity) {
270         // Not yet supported
271         return false;
272     }
273
274     public Severity getSeverity(Issue issue) {
275         return issue.getDefaultSeverity();
276     }
277 }