diff options
Diffstat (limited to 'lint/cli/src/com/android/tools/lint/Main.java')
-rw-r--r-- | lint/cli/src/com/android/tools/lint/Main.java | 1399 |
1 files changed, 0 insertions, 1399 deletions
diff --git a/lint/cli/src/com/android/tools/lint/Main.java b/lint/cli/src/com/android/tools/lint/Main.java deleted file mode 100644 index a33890d..0000000 --- a/lint/cli/src/com/android/tools/lint/Main.java +++ /dev/null @@ -1,1399 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * 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.android.tools.lint; - -import static com.android.tools.lint.client.api.IssueRegistry.LINT_ERROR; -import static com.android.tools.lint.client.api.IssueRegistry.PARSER_ERROR; -import static com.android.SdkConstants.DOT_XML; -import static com.android.tools.lint.detector.api.LintUtils.endsWith; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.lint.checks.BuiltinIssueRegistry; -import com.android.tools.lint.client.api.Configuration; -import com.android.tools.lint.client.api.DefaultConfiguration; -import com.android.tools.lint.client.api.IDomParser; -import com.android.tools.lint.client.api.IJavaParser; -import com.android.tools.lint.client.api.IssueRegistry; -import com.android.tools.lint.client.api.LintClient; -import com.android.tools.lint.client.api.LintDriver; -import com.android.tools.lint.client.api.LintListener; -import com.android.tools.lint.detector.api.Category; -import com.android.tools.lint.detector.api.Context; -import com.android.tools.lint.detector.api.Issue; -import com.android.tools.lint.detector.api.LintUtils; -import com.android.tools.lint.detector.api.Location; -import com.android.tools.lint.detector.api.Position; -import com.android.tools.lint.detector.api.Project; -import com.android.tools.lint.detector.api.Severity; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import com.google.common.io.Closeables; - -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileWriter; -import java.io.IOException; -import java.io.PrintStream; -import java.io.PrintWriter; -import java.io.Writer; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Properties; -import java.util.Set; - -/** - * Command line driver for the lint framework - */ -public class Main extends LintClient { - static final int MAX_LINE_WIDTH = 78; - private static final String ARG_ENABLE = "--enable"; //$NON-NLS-1$ - private static final String ARG_DISABLE = "--disable"; //$NON-NLS-1$ - private static final String ARG_CHECK = "--check"; //$NON-NLS-1$ - private static final String ARG_IGNORE = "--ignore"; //$NON-NLS-1$ - private static final String ARG_LISTIDS = "--list"; //$NON-NLS-1$ - private static final String ARG_SHOW = "--show"; //$NON-NLS-1$ - private static final String ARG_QUIET = "--quiet"; //$NON-NLS-1$ - private static final String ARG_FULLPATH = "--fullpath"; //$NON-NLS-1$ - private static final String ARG_SHOWALL = "--showall"; //$NON-NLS-1$ - private static final String ARG_HELP = "--help"; //$NON-NLS-1$ - private static final String ARG_NOLINES = "--nolines"; //$NON-NLS-1$ - private static final String ARG_HTML = "--html"; //$NON-NLS-1$ - private static final String ARG_SIMPLEHTML = "--simplehtml"; //$NON-NLS-1$ - private static final String ARG_XML = "--xml"; //$NON-NLS-1$ - private static final String ARG_TEXT = "--text"; //$NON-NLS-1$ - private static final String ARG_CONFIG = "--config"; //$NON-NLS-1$ - private static final String ARG_URL = "--url"; //$NON-NLS-1$ - private static final String ARG_VERSION = "--version"; //$NON-NLS-1$ - private static final String ARG_EXITCODE = "--exitcode"; //$NON-NLS-1$ - private static final String ARG_CLASSES = "--classpath"; //$NON-NLS-1$ - private static final String ARG_SOURCES = "--sources"; //$NON-NLS-1$ - private static final String ARG_LIBRARIES = "--libraries"; //$NON-NLS-1$ - - private static final String ARG_NOWARN2 = "--nowarn"; //$NON-NLS-1$ - // GCC style flag names for options - private static final String ARG_NOWARN1 = "-w"; //$NON-NLS-1$ - private static final String ARG_WARNALL = "-Wall"; //$NON-NLS-1$ - private static final String ARG_ALLERROR = "-Werror"; //$NON-NLS-1$ - - private static final String VALUE_NONE = "none"; //$NON-NLS-1$ - - private static final String PROP_WORK_DIR = "com.android.tools.lint.workdir"; //$NON-NLS-1$ - - private static final int ERRNO_ERRORS = 1; - private static final int ERRNO_USAGE = 2; - private static final int ERRNO_EXISTS = 3; - private static final int ERRNO_HELP = 4; - private static final int ERRNO_INVALIDARGS = 5; - - protected List<Warning> mWarnings = new ArrayList<Warning>(); - protected Set<String> mSuppress = new HashSet<String>(); - protected Set<String> mEnabled = new HashSet<String>(); - /** If non-null, only run the specified checks (possibly modified by enable/disables) */ - protected Set<String> mCheck = null; - protected boolean mHasErrors; - protected boolean mSetExitCode; - protected boolean mFullPath; - protected int mErrorCount; - protected int mWarningCount; - protected boolean mShowLines = true; - protected List<Reporter> mReporters = Lists.newArrayList(); - protected boolean mQuiet; - protected boolean mWarnAll; - protected boolean mNoWarnings; - protected boolean mAllErrors; - protected List<File> mSources; - protected List<File> mClasses; - protected List<File> mLibraries; - - protected Configuration mDefaultConfiguration; - protected IssueRegistry mRegistry; - protected LintDriver mDriver; - protected boolean mShowAll; - - /** Creates a CLI driver */ - public Main() { - } - - /** - * Runs the static analysis command line driver - * - * @param args program arguments - */ - public static void main(String[] args) { - new Main().run(args); - } - - /** - * Runs the static analysis command line driver - * - * @param args program arguments - */ - private void run(String[] args) { - if (args.length < 1) { - printUsage(System.err); - System.exit(ERRNO_USAGE); - } - - IssueRegistry registry = mRegistry = new BuiltinIssueRegistry(); - - // Mapping from file path prefix to URL. Applies only to HTML reports - String urlMap = null; - - List<File> files = new ArrayList<File>(); - for (int index = 0; index < args.length; index++) { - String arg = args[index]; - - if (arg.equals(ARG_HELP) - || arg.equals("-h") || arg.equals("-?")) { //$NON-NLS-1$ //$NON-NLS-2$ - if (index < args.length - 1) { - String topic = args[index + 1]; - if (topic.equals("suppress") || topic.equals("ignore")) { - printHelpTopicSuppress(); - System.exit(ERRNO_HELP); - } else { - System.err.println(String.format("Unknown help topic \"%1$s\"", topic)); - System.exit(ERRNO_INVALIDARGS); - } - } - printUsage(System.out); - System.exit(ERRNO_HELP); - } else if (arg.equals(ARG_LISTIDS)) { - // Did the user provide a category list? - if (index < args.length - 1 && !args[index + 1].startsWith("-")) { //$NON-NLS-1$ - String[] ids = args[++index].split(","); - for (String id : ids) { - if (registry.isCategoryName(id)) { - // List all issues with the given category - String category = id; - for (Issue issue : registry.getIssues()) { - // Check prefix such that filtering on the "Usability" category - // will match issue category "Usability:Icons" etc. - if (issue.getCategory().getName().startsWith(category) || - issue.getCategory().getFullName().startsWith(category)) { - listIssue(System.out, issue); - } - } - } else { - System.err.println("Invalid category \"" + id + "\".\n"); - displayValidIds(registry, System.err); - System.exit(ERRNO_INVALIDARGS); - } - } - } else { - displayValidIds(registry, System.out); - } - System.exit(0); - } else if (arg.equals(ARG_SHOW)) { - // Show specific issues? - if (index < args.length - 1 && !args[index + 1].startsWith("-")) { //$NON-NLS-1$ - String[] ids = args[++index].split(","); - for (String id : ids) { - if (registry.isCategoryName(id)) { - // Show all issues in the given category - String category = id; - for (Issue issue : registry.getIssues()) { - // Check prefix such that filtering on the "Usability" category - // will match issue category "Usability:Icons" etc. - if (issue.getCategory().getName().startsWith(category) || - issue.getCategory().getFullName().startsWith(category)) { - describeIssue(issue); - System.out.println(); - } - } - } else if (registry.isIssueId(id)) { - describeIssue(registry.getIssue(id)); - System.out.println(); - } else { - System.err.println("Invalid id or category \"" + id + "\".\n"); - displayValidIds(registry, System.err); - System.exit(ERRNO_INVALIDARGS); - } - } - } else { - showIssues(registry); - } - System.exit(0); - } else if (arg.equals(ARG_FULLPATH) - || arg.equals(ARG_FULLPATH + "s")) { // allow "--fullpaths" too - mFullPath = true; - } else if (arg.equals(ARG_SHOWALL)) { - mShowAll = true; - } else if (arg.equals(ARG_QUIET) || arg.equals("-q")) { - mQuiet = true; - } else if (arg.equals(ARG_NOLINES)) { - mShowLines = false; - } else if (arg.equals(ARG_EXITCODE)) { - mSetExitCode = true; - } else if (arg.equals(ARG_VERSION)) { - printVersion(); - System.exit(0); - } else if (arg.equals(ARG_URL)) { - if (index == args.length - 1) { - System.err.println("Missing URL mapping string"); - System.exit(ERRNO_INVALIDARGS); - } - String map = args[++index]; - // Allow repeated usage of the argument instead of just comma list - if (urlMap != null) { - urlMap = urlMap + ',' + map; - } else { - urlMap = map; - } - } else if (arg.equals(ARG_CONFIG)) { - if (index == args.length - 1 || !endsWith(args[index + 1], DOT_XML)) { - System.err.println("Missing XML configuration file argument"); - System.exit(ERRNO_INVALIDARGS); - } - File file = getInArgumentPath(args[++index]); - if (!file.exists()) { - System.err.println(file.getAbsolutePath() + " does not exist"); - System.exit(ERRNO_INVALIDARGS); - } - mDefaultConfiguration = new CliConfiguration(file); - } else if (arg.equals(ARG_HTML) || arg.equals(ARG_SIMPLEHTML)) { - if (index == args.length - 1) { - System.err.println("Missing HTML output file name"); - System.exit(ERRNO_INVALIDARGS); - } - File output = getOutArgumentPath(args[++index]); - // Get an absolute path such that we can ask its parent directory for - // write permission etc. - output = output.getAbsoluteFile(); - if (output.isDirectory() || - (!output.exists() && output.getName().indexOf('.') == -1)) { - if (!output.exists()) { - boolean mkdirs = output.mkdirs(); - if (!mkdirs) { - log(null, "Could not create output directory %1$s", output); - System.exit(ERRNO_EXISTS); - } - } - try { - MultiProjectHtmlReporter reporter = - new MultiProjectHtmlReporter(this, output); - if (arg.equals(ARG_SIMPLEHTML)) { - reporter.setSimpleFormat(true); - } - mReporters.add(reporter); - } catch (IOException e) { - log(e, null); - System.exit(ERRNO_INVALIDARGS); - } - continue; - } - if (output.exists()) { - boolean delete = output.delete(); - if (!delete) { - System.err.println("Could not delete old " + output); - System.exit(ERRNO_EXISTS); - } - } - if (output.getParentFile() != null && !output.getParentFile().canWrite()) { - System.err.println("Cannot write HTML output file " + output); - System.exit(ERRNO_EXISTS); - } - try { - HtmlReporter htmlReporter = new HtmlReporter(this, output); - if (arg.equals(ARG_SIMPLEHTML)) { - htmlReporter.setSimpleFormat(true); - } - mReporters.add(htmlReporter); - } catch (IOException e) { - log(e, null); - System.exit(ERRNO_INVALIDARGS); - } - } else if (arg.equals(ARG_XML)) { - if (index == args.length - 1) { - System.err.println("Missing XML output file name"); - System.exit(ERRNO_INVALIDARGS); - } - File output = getOutArgumentPath(args[++index]); - if (output.exists()) { - boolean delete = output.delete(); - if (!delete) { - System.err.println("Could not delete old " + output); - System.exit(ERRNO_EXISTS); - } - } - if (output.canWrite()) { - System.err.println("Cannot write XML output file " + output); - System.exit(ERRNO_EXISTS); - } - try { - mReporters.add(new XmlReporter(this, output)); - } catch (IOException e) { - log(e, null); - System.exit(ERRNO_INVALIDARGS); - } - } else if (arg.equals(ARG_TEXT)) { - if (index == args.length - 1) { - System.err.println("Missing XML output file name"); - System.exit(ERRNO_INVALIDARGS); - } - - Writer writer = null; - boolean closeWriter; - String outputName = args[++index]; - if (outputName.equals("stdout")) { //$NON-NLS-1$ - writer = new PrintWriter(System.out, true); - closeWriter = false; - } else { - File output = getOutArgumentPath(outputName); - if (output.exists()) { - boolean delete = output.delete(); - if (!delete) { - System.err.println("Could not delete old " + output); - System.exit(ERRNO_EXISTS); - } - } - if (output.canWrite()) { - System.err.println("Cannot write XML output file " + output); - System.exit(ERRNO_EXISTS); - } - try { - writer = new BufferedWriter(new FileWriter(output)); - } catch (IOException e) { - log(e, null); - System.exit(ERRNO_INVALIDARGS); - } - closeWriter = true; - } - mReporters.add(new TextReporter(this, writer, closeWriter)); - } else if (arg.equals(ARG_DISABLE) || arg.equals(ARG_IGNORE)) { - if (index == args.length - 1) { - System.err.println("Missing categories or id's to disable"); - System.exit(ERRNO_INVALIDARGS); - } - String[] ids = args[++index].split(","); - for (String id : ids) { - if (registry.isCategoryName(id)) { - // Suppress all issues with the given category - String category = id; - for (Issue issue : registry.getIssues()) { - // Check prefix such that filtering on the "Usability" category - // will match issue category "Usability:Icons" etc. - if (issue.getCategory().getName().startsWith(category) || - issue.getCategory().getFullName().startsWith(category)) { - mSuppress.add(issue.getId()); - } - } - } else if (!registry.isIssueId(id)) { - System.err.println("Invalid id or category \"" + id + "\".\n"); - displayValidIds(registry, System.err); - System.exit(ERRNO_INVALIDARGS); - } else { - mSuppress.add(id); - } - } - } else if (arg.equals(ARG_ENABLE)) { - if (index == args.length - 1) { - System.err.println("Missing categories or id's to enable"); - System.exit(ERRNO_INVALIDARGS); - } - String[] ids = args[++index].split(","); - for (String id : ids) { - if (registry.isCategoryName(id)) { - // Enable all issues with the given category - String category = id; - for (Issue issue : registry.getIssues()) { - if (issue.getCategory().getName().startsWith(category) || - issue.getCategory().getFullName().startsWith(category)) { - mEnabled.add(issue.getId()); - } - } - } else if (!registry.isIssueId(id)) { - System.err.println("Invalid id or category \"" + id + "\".\n"); - displayValidIds(registry, System.err); - System.exit(ERRNO_INVALIDARGS); - } else { - mEnabled.add(id); - } - } - } else if (arg.equals(ARG_CHECK)) { - if (index == args.length - 1) { - System.err.println("Missing categories or id's to check"); - System.exit(ERRNO_INVALIDARGS); - } - mCheck = new HashSet<String>(); - String[] ids = args[++index].split(","); - for (String id : ids) { - if (registry.isCategoryName(id)) { - // Check all issues with the given category - String category = id; - for (Issue issue : registry.getIssues()) { - // Check prefix such that filtering on the "Usability" category - // will match issue category "Usability:Icons" etc. - if (issue.getCategory().getName().startsWith(category) || - issue.getCategory().getFullName().startsWith(category)) { - mCheck.add(issue.getId()); - } - } - } else if (!registry.isIssueId(id)) { - System.err.println("Invalid id or category \"" + id + "\".\n"); - displayValidIds(registry, System.err); - System.exit(ERRNO_INVALIDARGS); - } else { - mCheck.add(id); - } - } - } else if (arg.equals(ARG_NOWARN1) || arg.equals(ARG_NOWARN2)) { - mNoWarnings = true; - } else if (arg.equals(ARG_WARNALL)) { - mWarnAll = true; - } else if (arg.equals(ARG_ALLERROR)) { - mAllErrors = true; - } else if (arg.equals(ARG_CLASSES)) { - if (index == args.length - 1) { - System.err.println("Missing class folder name"); - System.exit(ERRNO_INVALIDARGS); - } - String paths = args[++index]; - for (String path : LintUtils.splitPath(paths)) { - File input = getInArgumentPath(path); - if (!input.exists()) { - System.err.println("Class path entry " + input + " does not exist."); - System.exit(ERRNO_INVALIDARGS); - } - if (mClasses == null) { - mClasses = new ArrayList<File>(); - } - mClasses.add(input); - } - } else if (arg.equals(ARG_SOURCES)) { - if (index == args.length - 1) { - System.err.println("Missing source folder name"); - System.exit(ERRNO_INVALIDARGS); - } - String paths = args[++index]; - for (String path : LintUtils.splitPath(paths)) { - File input = getInArgumentPath(path); - if (!input.exists()) { - System.err.println("Source folder " + input + " does not exist."); - System.exit(ERRNO_INVALIDARGS); - } - if (mSources == null) { - mSources = new ArrayList<File>(); - } - mSources.add(input); - } - } else if (arg.equals(ARG_LIBRARIES)) { - if (index == args.length - 1) { - System.err.println("Missing library folder name"); - System.exit(ERRNO_INVALIDARGS); - } - String paths = args[++index]; - for (String path : LintUtils.splitPath(paths)) { - File input = getInArgumentPath(path); - if (!input.exists()) { - System.err.println("Library " + input + " does not exist."); - System.exit(ERRNO_INVALIDARGS); - } - if (mLibraries == null) { - mLibraries = new ArrayList<File>(); - } - mLibraries.add(input); - } - } else if (arg.startsWith("--")) { - System.err.println("Invalid argument " + arg + "\n"); - printUsage(System.err); - System.exit(ERRNO_INVALIDARGS); - } else { - String filename = arg; - File file = getInArgumentPath(filename); - - if (!file.exists()) { - System.err.println(String.format("%1$s does not exist.", filename)); - System.exit(ERRNO_EXISTS); - } - files.add(file); - } - } - - if (files.size() == 0) { - System.err.println("No files to analyze."); - System.exit(ERRNO_INVALIDARGS); - } else if (files.size() > 1 - && (mClasses != null || mSources != null || mLibraries != null)) { - System.err.println("The " + ARG_SOURCES + ", " + ARG_CLASSES + " and " - + ARG_LIBRARIES + " arguments can only be used with a single project"); - System.exit(ERRNO_INVALIDARGS); - } - - if (mReporters.isEmpty()) { - if (urlMap != null) { - System.err.println(String.format( - "Warning: The %1$s option only applies to HTML reports (%2$s)", - ARG_URL, ARG_HTML)); - } - - mReporters.add(new TextReporter(this, new PrintWriter(System.out, true), false)); - } else { - if (urlMap == null) { - // By default just map from /foo to file:///foo - // TODO: Find out if we need file:// on Windows. - urlMap = "=file://"; //$NON-NLS-1$ - } else { - for (Reporter reporter : mReporters) { - if (!reporter.isSimpleFormat()) { - reporter.setBundleResources(true); - } - } - } - - if (!urlMap.equals(VALUE_NONE)) { - Map<String, String> map = new HashMap<String, String>(); - String[] replace = urlMap.split(","); //$NON-NLS-1$ - for (String s : replace) { - // Allow ='s in the suffix part - int index = s.indexOf('='); - if (index == -1) { - System.err.println( - "The URL map argument must be of the form 'path_prefix=url_prefix'"); - System.exit(ERRNO_INVALIDARGS); - } - String key = s.substring(0, index); - String value = s.substring(index + 1); - map.put(key, value); - } - for (Reporter reporter : mReporters) { - reporter.setUrlMap(map); - } - } - } - - mDriver = new LintDriver(registry, this); - - mDriver.setAbbreviating(!mShowAll); - if (!mQuiet) { - mDriver.addLintListener(new ProgressPrinter()); - } - - mDriver.analyze(files, null /* scope */); - - Collections.sort(mWarnings); - - for (Reporter reporter : mReporters) { - try { - reporter.write(mErrorCount, mWarningCount, mWarnings); - } catch (IOException e) { - log(e, null); - System.exit(ERRNO_INVALIDARGS); - } - } - - System.exit(mSetExitCode ? (mHasErrors ? ERRNO_ERRORS : 0) : 0); - } - - /** - * Converts a relative or absolute command-line argument into an input file. - * - * @param filename The filename given as a command-line argument. - * @return A File matching filename, either absolute or relative to lint.workdir if defined. - */ - private File getInArgumentPath(String filename) { - File file = new File(filename); - - if (!file.isAbsolute()) { - File workDir = getLintWorkDir(); - if (workDir != null) { - File file2 = new File(workDir, filename); - if (file2.exists()) { - try { - file = file2.getCanonicalFile(); - } catch (IOException e) { - file = file2; - } - } - } - } - return file; - } - - /** - * Converts a relative or absolute command-line argument into an output file. - * <p/> - * The difference with {@code getInArgumentPath} is that we can't check whether the - * a relative path turned into an absolute compared to lint.workdir actually exists. - * - * @param filename The filename given as a command-line argument. - * @return A File matching filename, either absolute or relative to lint.workdir if defined. - */ - private File getOutArgumentPath(String filename) { - File file = new File(filename); - - if (!file.isAbsolute()) { - File workDir = getLintWorkDir(); - if (workDir != null) { - File file2 = new File(workDir, filename); - try { - file = file2.getCanonicalFile(); - } catch (IOException e) { - file = file2; - } - } - } - return file; - } - - - /** - * Returns the File corresponding to the system property or the environment variable - * for {@link #PROP_WORK_DIR}. - * This property is typically set by the SDK/tools/lint[.bat] wrapper. - * It denotes the path where the command-line client was originally invoked from - * and can be used to convert relative input/output paths. - * - * @return A new File corresponding to {@link #PROP_WORK_DIR} or null. - */ - @Nullable - private File getLintWorkDir() { - // First check the Java properties (e.g. set using "java -jar ... -Dname=value") - String path = System.getProperty(PROP_WORK_DIR); - if (path == null || path.length() == 0) { - // If not found, check environment variables. - path = System.getenv(PROP_WORK_DIR); - } - if (path != null && path.length() > 0) { - return new File(path); - } - return null; - } - - private void printHelpTopicSuppress() { - System.out.println(wrap(getSuppressHelp())); - } - - static String getSuppressHelp() { - return - "Lint errors can be suppressed in a variety of ways:\n" + - "\n" + - "1. With a @SuppressLint annotation in the Java code\n" + - "2. With a tools:ignore attribute in the XML file\n" + - "3. With a lint.xml configuration file in the project\n" + - "4. With a lint.xml configuration file passed to lint " + - "via the " + ARG_CONFIG + " flag\n" + - "5. With the " + ARG_IGNORE + " flag passed to lint.\n" + - "\n" + - "To suppress a lint warning with an annotation, add " + - "a @SuppressLint(\"id\") annotation on the class, method " + - "or variable declaration closest to the warning instance " + - "you want to disable. The id can be one or more issue " + - "id's, such as \"UnusedResources\" or {\"UnusedResources\"," + - "\"UnusedIds\"}, or it can be \"all\" to suppress all lint " + - "warnings in the given scope.\n" + - "\n" + - "To suppress a lint warning in an XML file, add a " + - "tools:ignore=\"id\" attribute on the element containing " + - "the error, or one of its surrounding elements. You also " + - "need to define the namespace for the tools prefix on the " + - "root element in your document, next to the xmlns:android " + - "declaration:\n" + - "* xmlns:tools=\"http://schemas.android.com/tools\"\n" + - "\n" + - "To suppress lint warnings with a configuration XML file, " + - "create a file named lint.xml and place it at the root " + - "directory of the project in which it applies. (If you " + - "use the Eclipse plugin's Lint view, you can suppress " + - "errors there via the toolbar and Eclipse will create the " + - "lint.xml file for you.).\n" + - "\n" + - "The format of the lint.xml file is something like the " + - "following:\n" + - "\n" + - "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + - "<lint>\n" + - " <!-- Disable this given check in this project -->\n" + - " <issue id=\"IconMissingDensityFolder\" severity=\"ignore\" />\n" + - "\n" + - " <!-- Ignore the ObsoleteLayoutParam issue in the given files -->\n" + - " <issue id=\"ObsoleteLayoutParam\">\n" + - " <ignore path=\"res/layout/activation.xml\" />\n" + - " <ignore path=\"res/layout-xlarge/activation.xml\" />\n" + - " </issue>\n" + - "\n" + - " <!-- Ignore the UselessLeaf issue in the given file -->\n" + - " <issue id=\"UselessLeaf\">\n" + - " <ignore path=\"res/layout/main.xml\" />\n" + - " </issue>\n" + - "\n" + - " <!-- Change the severity of hardcoded strings to \"error\" -->\n" + - " <issue id=\"HardcodedText\" severity=\"error\" />\n" + - "</lint>\n" + - "\n" + - "To suppress lint checks from the command line, pass the " + ARG_IGNORE + " " + - "flag with a comma separated list of ids to be suppressed, such as:\n" + - "\"lint --ignore UnusedResources,UselessLeaf /my/project/path\"\n"; - } - - private void printVersion() { - String revision = getRevision(); - if (revision != null) { - System.out.println(String.format("lint: version %1$s", revision)); - } else { - System.out.println("lint: unknown version"); - } - } - - @SuppressWarnings("resource") // Eclipse doesn't know about Closeables.closeQuietly - @Nullable - String getRevision() { - File file = findResource("tools" + File.separator + //$NON-NLS-1$ - "source.properties"); //$NON-NLS-1$ - if (file != null && file.exists()) { - FileInputStream input = null; - try { - input = new FileInputStream(file); - Properties properties = new Properties(); - properties.load(input); - - String revision = properties.getProperty("Pkg.Revision"); //$NON-NLS-1$ - if (revision != null && revision.length() > 0) { - return revision; - } - } catch (IOException e) { - // Couldn't find or read the version info: just print out unknown below - } finally { - Closeables.closeQuietly(input); - } - } - - return null; - } - - private void displayValidIds(IssueRegistry registry, PrintStream out) { - List<Category> categories = registry.getCategories(); - out.println("Valid issue categories:"); - for (Category category : categories) { - out.println(" " + category.getFullName()); - } - out.println(); - List<Issue> issues = registry.getIssues(); - out.println("Valid issue id's:"); - for (Issue issue : issues) { - listIssue(out, issue); - } - } - - private void listIssue(PrintStream out, Issue issue) { - out.print(wrapArg("\"" + issue.getId() + "\": " + issue.getDescription())); - } - - private void showIssues(IssueRegistry registry) { - List<Issue> issues = registry.getIssues(); - List<Issue> sorted = new ArrayList<Issue>(issues); - Collections.sort(sorted, new Comparator<Issue>() { - @Override - public int compare(Issue issue1, Issue issue2) { - int d = issue1.getCategory().compareTo(issue2.getCategory()); - if (d != 0) { - return d; - } - d = issue2.getPriority() - issue1.getPriority(); - if (d != 0) { - return d; - } - - return issue1.getId().compareTo(issue2.getId()); - } - }); - - System.out.println("Available issues:\n"); - Category previousCategory = null; - for (Issue issue : sorted) { - Category category = issue.getCategory(); - if (!category.equals(previousCategory)) { - String name = category.getFullName(); - System.out.println(name); - for (int i = 0, n = name.length(); i < n; i++) { - System.out.print('='); - } - System.out.println('\n'); - previousCategory = category; - } - - describeIssue(issue); - System.out.println(); - } - } - - private void describeIssue(Issue issue) { - System.out.println(issue.getId()); - for (int i = 0; i < issue.getId().length(); i++) { - System.out.print('-'); - } - System.out.println(); - System.out.println(wrap("Summary: " + issue.getDescription())); - System.out.println("Priority: " + issue.getPriority() + " / 10"); - System.out.println("Severity: " + issue.getDefaultSeverity().getDescription()); - System.out.println("Category: " + issue.getCategory().getFullName()); - - if (!issue.isEnabledByDefault()) { - System.out.println("NOTE: This issue is disabled by default!"); - System.out.println(String.format("You can enable it by adding %1$s %2$s", ARG_ENABLE, - issue.getId())); - } - - if (issue.getExplanation() != null) { - System.out.println(); - System.out.println(wrap(issue.getExplanationAsSimpleText())); - } - if (issue.getMoreInfo() != null) { - System.out.println("More information: " + issue.getMoreInfo()); - } - } - - static String wrapArg(String explanation) { - // Wrap arguments such that the wrapped lines are not showing up in the left column - return wrap(explanation, MAX_LINE_WIDTH, " "); - } - - static String wrap(String explanation) { - return wrap(explanation, MAX_LINE_WIDTH, ""); - } - - static String wrap(String explanation, int lineWidth, String hangingIndent) { - int explanationLength = explanation.length(); - StringBuilder sb = new StringBuilder(explanationLength * 2); - int index = 0; - - while (index < explanationLength) { - int lineEnd = explanation.indexOf('\n', index); - int next; - - if (lineEnd != -1 && (lineEnd - index) < lineWidth) { - next = lineEnd + 1; - } else { - // Line is longer than available width; grab as much as we can - lineEnd = Math.min(index + lineWidth, explanationLength); - if (lineEnd - index < lineWidth) { - next = explanationLength; - } else { - // then back up to the last space - int lastSpace = explanation.lastIndexOf(' ', lineEnd); - if (lastSpace > index) { - lineEnd = lastSpace; - next = lastSpace + 1; - } else { - // No space anywhere on the line: it contains something wider than - // can fit (like a long URL) so just hard break it - next = lineEnd + 1; - } - } - } - - if (sb.length() > 0) { - sb.append(hangingIndent); - } else { - lineWidth -= hangingIndent.length(); - } - - sb.append(explanation.substring(index, lineEnd)); - sb.append('\n'); - index = next; - } - - return sb.toString(); - } - - private static void printUsage(PrintStream out) { - // TODO: Look up launcher script name! - String command = "lint"; //$NON-NLS-1$ - - out.println("Usage: " + command + " [flags] <project directories>\n"); - out.println("Flags:\n"); - - printUsage(out, new String[] { - ARG_HELP, "This message.", - ARG_HELP + " <topic>", "Help on the given topic, such as \"suppress\".", - ARG_LISTIDS, "List the available issue id's and exit.", - ARG_VERSION, "Output version information and exit.", - ARG_EXITCODE, "Set the exit code to " + ERRNO_ERRORS + " if errors are found.", - ARG_SHOW, "List available issues along with full explanations.", - ARG_SHOW + " <ids>", "Show full explanations for the given list of issue id's.", - - "", "\nEnabled Checks:", - ARG_DISABLE + " <list>", "Disable the list of categories or " + - "specific issue id's. The list should be a comma-separated list of issue " + - "id's or categories.", - ARG_ENABLE + " <list>", "Enable the specific list of issues. " + - "This checks all the default issues plus the specifically enabled issues. The " + - "list should be a comma-separated list of issue id's or categories.", - ARG_CHECK + " <list>", "Only check the specific list of issues. " + - "This will disable everything and re-enable the given list of issues. " + - "The list should be a comma-separated list of issue id's or categories.", - ARG_NOWARN1 + ", " + ARG_NOWARN2, "Only check for errors (ignore warnings)", - ARG_WARNALL, "Check all warnings, including those off by default", - ARG_ALLERROR, "Treat all warnings as errors", - ARG_CONFIG + " <filename>", "Use the given configuration file to " + - "determine whether issues are enabled or disabled. If a project contains " + - "a lint.xml file, then this config file will be used as a fallback.", - - - "", "\nOutput Options:", - ARG_QUIET, "Don't show progress.", - ARG_FULLPATH, "Use full paths in the error output.", - ARG_SHOWALL, "Do not truncate long messages, lists of alternate locations, etc.", - ARG_NOLINES, "Do not include the source file lines with errors " + - "in the output. By default, the error output includes snippets of source code " + - "on the line containing the error, but this flag turns it off.", - ARG_HTML + " <filename>", "Create an HTML report instead. If the filename is a " + - "directory (or a new filename without an extension), lint will create a " + - "separate report for each scanned project.", - ARG_URL + " filepath=url", "Add links to HTML report, replacing local " + - "path prefixes with url prefix. The mapping can be a comma-separated list of " + - "path prefixes to corresponding URL prefixes, such as " + - "C:\\temp\\Proj1=http://buildserver/sources/temp/Proj1. To turn off linking " + - "to files, use " + ARG_URL + " " + VALUE_NONE, - ARG_SIMPLEHTML + " <filename>", "Create a simple HTML report", - ARG_XML + " <filename>", "Create an XML report instead.", - - "", "\nProject Options:", - ARG_SOURCES + " <dir>", "Add the given folder (or path) as a source directory for " + - "the project. Only valid when running lint on a single project.", - ARG_CLASSES + " <dir>", "Add the given folder (or jar file, or path) as a class " + - "directory for the project. Only valid when running lint on a single project.", - ARG_LIBRARIES + " <dir>", "Add the given folder (or jar file, or path) as a class " + - "library for the project. Only valid when running lint on a single project.", - - "", "\nExit Status:", - "0", "Success.", - Integer.toString(ERRNO_ERRORS), "Lint errors detected.", - Integer.toString(ERRNO_USAGE), "Lint usage.", - Integer.toString(ERRNO_EXISTS), "Cannot clobber existing file.", - Integer.toString(ERRNO_HELP), "Lint help.", - Integer.toString(ERRNO_INVALIDARGS), "Invalid command-line argument.", - }); - } - - private static void printUsage(PrintStream out, String[] args) { - int argWidth = 0; - for (int i = 0; i < args.length; i += 2) { - String arg = args[i]; - argWidth = Math.max(argWidth, arg.length()); - } - argWidth += 2; - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < argWidth; i++) { - sb.append(' '); - } - String indent = sb.toString(); - String formatString = "%1$-" + argWidth + "s%2$s"; //$NON-NLS-1$ - - for (int i = 0; i < args.length; i += 2) { - String arg = args[i]; - String description = args[i + 1]; - if (arg.length() == 0) { - out.println(description); - } else { - out.print(wrap(String.format(formatString, arg, description), - MAX_LINE_WIDTH, indent)); - } - } - } - - @Override - public void log( - @NonNull Severity severity, - @Nullable Throwable exception, - @Nullable String format, - @Nullable Object... args) { - System.out.flush(); - if (!mQuiet) { - // Place the error message on a line of its own since we're printing '.' etc - // with newlines during analysis - System.err.println(); - } - if (format != null) { - System.err.println(String.format(format, args)); - } - if (exception != null) { - exception.printStackTrace(); - } - } - - @Override - public IDomParser getDomParser() { - return new LintCliXmlParser(); - } - - @Override - public Configuration getConfiguration(@NonNull Project project) { - return new CliConfiguration(mDefaultConfiguration, project); - } - - /** File content cache */ - private Map<File, String> mFileContents = new HashMap<File, String>(100); - - /** Read the contents of the given file, possibly cached */ - private String getContents(File file) { - String s = mFileContents.get(file); - if (s == null) { - s = readFile(file); - mFileContents.put(file, s); - } - - return s; - } - - @Override - public IJavaParser getJavaParser() { - return new LombokParser(); - } - - @Override - public void report( - @NonNull Context context, - @NonNull Issue issue, - @NonNull Severity severity, - @Nullable Location location, - @NonNull String message, - @Nullable Object data) { - assert context.isEnabled(issue); - - if (severity == Severity.IGNORE) { - return; - } - - if (severity == Severity.ERROR || severity == Severity.FATAL) { - mHasErrors = true; - mErrorCount++; - } else { - mWarningCount++; - } - - Warning warning = new Warning(issue, message, severity, context.getProject(), data); - mWarnings.add(warning); - - if (location != null) { - warning.location = location; - File file = location.getFile(); - if (file != null) { - warning.file = file; - warning.path = getDisplayPath(context.getProject(), file); - } - - Position startPosition = location.getStart(); - if (startPosition != null) { - int line = startPosition.getLine(); - warning.line = line; - warning.offset = startPosition.getOffset(); - if (line >= 0) { - if (context.file == location.getFile()) { - warning.fileContents = context.getContents(); - } - if (warning.fileContents == null) { - warning.fileContents = getContents(location.getFile()); - } - - if (mShowLines) { - // Compute error line contents - warning.errorLine = getLine(warning.fileContents, line); - if (warning.errorLine != null) { - // Replace tabs with spaces such that the column - // marker (^) lines up properly: - warning.errorLine = warning.errorLine.replace('\t', ' '); - int column = startPosition.getColumn(); - if (column < 0) { - column = 0; - for (int i = 0; i < warning.errorLine.length(); i++, column++) { - if (!Character.isWhitespace(warning.errorLine.charAt(i))) { - break; - } - } - } - StringBuilder sb = new StringBuilder(); - sb.append(warning.errorLine); - sb.append('\n'); - for (int i = 0; i < column; i++) { - sb.append(' '); - } - - boolean displayCaret = true; - Position endPosition = location.getEnd(); - if (endPosition != null) { - int endLine = endPosition.getLine(); - int endColumn = endPosition.getColumn(); - if (endLine == line && endColumn > column) { - for (int i = column; i < endColumn; i++) { - sb.append("~"); - } - displayCaret = false; - } - } - - if (displayCaret) { - sb.append('^'); - } - sb.append('\n'); - warning.errorLine = sb.toString(); - } - } - } - } - } - } - - /** Look up the contents of the given line */ - static String getLine(String contents, int line) { - int index = getLineOffset(contents, line); - if (index != -1) { - return getLineOfOffset(contents, index); - } else { - return null; - } - } - - static String getLineOfOffset(String contents, int offset) { - int end = contents.indexOf('\n', offset); - if (end == -1) { - end = contents.indexOf('\r', offset); - } - return contents.substring(offset, end != -1 ? end : contents.length()); - } - - - /** Look up the contents of the given line */ - static int getLineOffset(String contents, int line) { - int index = 0; - for (int i = 0; i < line; i++) { - index = contents.indexOf('\n', index); - if (index == -1) { - return -1; - } - index++; - } - - return index; - } - - @Override - public @NonNull String readFile(@NonNull File file) { - try { - return LintUtils.getEncodedString(this, file); - } catch (IOException e) { - return ""; //$NON-NLS-1$ - } - } - - boolean isCheckingSpecificIssues() { - return mCheck != null; - } - - private Map<Project, ClassPathInfo> mProjectInfo; - - @Override - @NonNull - protected ClassPathInfo getClassPath(@NonNull Project project) { - ClassPathInfo classPath = super.getClassPath(project); - - if (mClasses == null && mSources == null && mLibraries == null) { - return classPath; - } - - ClassPathInfo info; - if (mProjectInfo == null) { - mProjectInfo = Maps.newHashMap(); - info = null; - } else { - info = mProjectInfo.get(project); - } - - if (info == null) { - List<File> sources; - if (mSources != null) { - sources = mSources; - } else { - sources = classPath.getSourceFolders(); - } - List<File> classes; - if (mClasses != null) { - classes = mClasses; - } else { - classes = classPath.getClassFolders(); - } - List<File> libraries; - if (mLibraries != null) { - libraries = mLibraries; - } else { - libraries = classPath.getLibraries(); - } - - info = new ClassPathInfo(sources, classes, libraries); - mProjectInfo.put(project, info); - } - - return info; - } - - /** - * Consult the lint.xml file, but override with the --enable and --disable - * flags supplied on the command line - */ - class CliConfiguration extends DefaultConfiguration { - CliConfiguration(@NonNull Configuration parent, @NonNull Project project) { - super(Main.this, project, parent); - } - - CliConfiguration(File lintFile) { - super(Main.this, null /*project*/, null /*parent*/, lintFile); - } - - @Override - public @NonNull Severity getSeverity(@NonNull Issue issue) { - Severity severity = computeSeverity(issue); - - if (mAllErrors && severity != Severity.IGNORE) { - severity = Severity.ERROR; - } - - if (mNoWarnings && severity == Severity.WARNING) { - severity = Severity.IGNORE; - } - - return severity; - } - - @Override - protected @NonNull Severity getDefaultSeverity(@NonNull Issue issue) { - if (mWarnAll) { - return issue.getDefaultSeverity(); - } - - return super.getDefaultSeverity(issue); - } - - private Severity computeSeverity(@NonNull Issue issue) { - Severity severity = super.getSeverity(issue); - - String id = issue.getId(); - if (mSuppress.contains(id)) { - return Severity.IGNORE; - } - - if (mEnabled.contains(id) || (mCheck != null && mCheck.contains(id))) { - // Overriding default - // Detectors shouldn't be returning ignore as a default severity, - // but in case they do, force it up to warning here to ensure that - // it's run - if (severity == Severity.IGNORE) { - severity = issue.getDefaultSeverity(); - if (severity == Severity.IGNORE) { - severity = Severity.WARNING; - } - } - - return severity; - } - - if (mCheck != null && issue != LINT_ERROR && issue != PARSER_ERROR) { - return Severity.IGNORE; - } - - return severity; - } - } - - private class ProgressPrinter implements LintListener { - @Override - public void update( - @NonNull LintDriver lint, - @NonNull EventType type, - @Nullable Context context) { - switch (type) { - case SCANNING_PROJECT: { - String name = context != null ? context.getProject().getName() : "?"; - if (lint.getPhase() > 1) { - System.out.print(String.format( - "\nScanning %1$s (Phase %2$d): ", - name, - lint.getPhase())); - } else { - System.out.print(String.format( - "\nScanning %1$s: ", - name)); - } - break; - } - case SCANNING_LIBRARY_PROJECT: { - String name = context != null ? context.getProject().getName() : "?"; - System.out.print(String.format( - "\n - %1$s: ", - name)); - break; - } - case SCANNING_FILE: - System.out.print('.'); - break; - case NEW_PHASE: - // Ignored for now: printing status as part of next project's status - break; - case CANCELED: - case COMPLETED: - System.out.println(); - break; - } - } - } - - String getDisplayPath(Project project, File file) { - String path = file.getPath(); - if (!mFullPath && path.startsWith(project.getReferenceDir().getPath())) { - int chop = project.getReferenceDir().getPath().length(); - if (path.length() > chop && path.charAt(chop) == File.separatorChar) { - chop++; - } - path = path.substring(chop); - if (path.length() == 0) { - path = file.getName(); - } - } - - return path; - } - - /** Returns whether all warnings are enabled, including those disabled by default */ - boolean isAllEnabled() { - return mWarnAll; - } - - /** Returns the issue registry used by this client */ - IssueRegistry getRegistry() { - return mRegistry; - } - - /** Returns the driver running the lint checks */ - LintDriver getDriver() { - return mDriver; - } - - /** Returns the configuration used by this client */ - Configuration getConfiguration() { - return mDefaultConfiguration; - } - - /** Returns true if the given issue has been explicitly disabled */ - boolean isSuppressed(Issue issue) { - return mSuppress.contains(issue.getId()); - } -} |