blob: 2d16e981df14edb72e50ece6e024fe45ac2c00b5 [file] [log] [blame]
/*
* Copyright (C) 2013 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.checks;
import static com.android.SdkConstants.ANDROID_MANIFEST_XML;
import static com.android.xml.AndroidManifest.NODE_ACTION;
import static com.android.xml.AndroidManifest.NODE_ACTIVITY;
import static com.android.xml.AndroidManifest.NODE_ACTIVITY_ALIAS;
import static com.android.xml.AndroidManifest.NODE_APPLICATION;
import static com.android.xml.AndroidManifest.NODE_CATEGORY;
import static com.android.xml.AndroidManifest.NODE_COMPATIBLE_SCREENS;
import static com.android.xml.AndroidManifest.NODE_DATA;
import static com.android.xml.AndroidManifest.NODE_GRANT_URI_PERMISSION;
import static com.android.xml.AndroidManifest.NODE_INSTRUMENTATION;
import static com.android.xml.AndroidManifest.NODE_INTENT;
import static com.android.xml.AndroidManifest.NODE_MANIFEST;
import static com.android.xml.AndroidManifest.NODE_METADATA;
import static com.android.xml.AndroidManifest.NODE_PATH_PERMISSION;
import static com.android.xml.AndroidManifest.NODE_PERMISSION;
import static com.android.xml.AndroidManifest.NODE_PERMISSION_GROUP;
import static com.android.xml.AndroidManifest.NODE_PERMISSION_TREE;
import static com.android.xml.AndroidManifest.NODE_PROVIDER;
import static com.android.xml.AndroidManifest.NODE_RECEIVER;
import static com.android.xml.AndroidManifest.NODE_SERVICE;
import static com.android.xml.AndroidManifest.NODE_SUPPORTS_GL_TEXTURE;
import static com.android.xml.AndroidManifest.NODE_SUPPORTS_SCREENS;
import static com.android.xml.AndroidManifest.NODE_USES_CONFIGURATION;
import static com.android.xml.AndroidManifest.NODE_USES_FEATURE;
import static com.android.xml.AndroidManifest.NODE_USES_LIBRARY;
import static com.android.xml.AndroidManifest.NODE_USES_PERMISSION;
import static com.android.xml.AndroidManifest.NODE_USES_SDK;
import com.android.annotations.NonNull;
import com.android.tools.lint.detector.api.Category;
import com.android.tools.lint.detector.api.Context;
import com.android.tools.lint.detector.api.Detector;
import com.android.tools.lint.detector.api.Implementation;
import com.android.tools.lint.detector.api.Issue;
import com.android.tools.lint.detector.api.LintUtils;
import com.android.tools.lint.detector.api.Scope;
import com.android.tools.lint.detector.api.Severity;
import com.android.tools.lint.detector.api.Speed;
import com.android.tools.lint.detector.api.XmlContext;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import org.w3c.dom.Element;
import java.io.File;
import java.util.Collection;
import java.util.List;
import java.util.Set;
/**
* Checks for typos in manifest files
*/
public class ManifestTypoDetector extends Detector implements Detector.XmlScanner {
/** The main issue discovered by this detector */
public static final Issue ISSUE = Issue.create(
"ManifestTypo", //$NON-NLS-1$
"Typos in manifest tags",
"This check looks through the manifest, and if it finds any tags " +
"that look like likely misspellings, they are flagged.",
Category.CORRECTNESS,
5,
Severity.FATAL,
new Implementation(
ManifestTypoDetector.class,
Scope.MANIFEST_SCOPE));
private static final Set<String> sValidTags;
static {
int expectedSize = 30;
sValidTags = Sets.newHashSetWithExpectedSize(expectedSize);
sValidTags.add(NODE_MANIFEST);
sValidTags.add(NODE_APPLICATION);
sValidTags.add(NODE_ACTIVITY);
sValidTags.add(NODE_SERVICE);
sValidTags.add(NODE_PROVIDER);
sValidTags.add(NODE_RECEIVER);
sValidTags.add(NODE_USES_FEATURE);
sValidTags.add(NODE_USES_LIBRARY);
sValidTags.add(NODE_USES_SDK);
sValidTags.add(NODE_INSTRUMENTATION);
sValidTags.add(NODE_USES_PERMISSION);
sValidTags.add(NODE_PERMISSION);
sValidTags.add(NODE_PERMISSION_TREE);
sValidTags.add(NODE_PERMISSION_GROUP);
sValidTags.add(NODE_USES_CONFIGURATION);
sValidTags.add(NODE_ACTIVITY_ALIAS);
sValidTags.add(NODE_INTENT);
sValidTags.add(NODE_METADATA);
sValidTags.add(NODE_ACTION);
sValidTags.add(NODE_CATEGORY);
sValidTags.add(NODE_DATA);
sValidTags.add(NODE_GRANT_URI_PERMISSION);
sValidTags.add(NODE_PATH_PERMISSION);
sValidTags.add(NODE_SUPPORTS_SCREENS);
sValidTags.add(NODE_COMPATIBLE_SCREENS);
sValidTags.add(NODE_SUPPORTS_GL_TEXTURE);
// Private tags
sValidTags.add("eat-comment"); //$NON-NLS-1$
sValidTags.add("original-package"); //$NON-NLS-1$
sValidTags.add("protected-broadcast"); //$NON-NLS-1$
sValidTags.add("adopt-permissions"); //$NON-NLS-1$
assert sValidTags.size() <= expectedSize : sValidTags.size();
}
/** Constructs a new {@link ManifestTypoDetector} check */
public ManifestTypoDetector() {
}
@NonNull
@Override
public Speed getSpeed() {
return Speed.FAST;
}
@Override
public boolean appliesTo(@NonNull Context context, @NonNull File file) {
return file.getName().equals(ANDROID_MANIFEST_XML);
}
@Override
public Collection<String> getApplicableElements() {
return XmlScanner.ALL;
}
private static final int MAX_EDIT_DISTANCE = 3;
@Override
public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
String tag = element.getTagName();
if (!sValidTags.contains(tag)) {
int tagLength = tag.length();
// Try to find the corresponding match
List<String> suggestions = null;
for (String suggestion : sValidTags) {
if (Math.abs(suggestion.length() - tagLength) > MAX_EDIT_DISTANCE) {
continue;
}
if (LintUtils.editDistance(suggestion, tag) <= MAX_EDIT_DISTANCE) {
if (suggestions == null) {
suggestions = Lists.newArrayList();
}
suggestions.add('<' + suggestion + '>');
}
}
if (suggestions != null) {
assert !suggestions.isEmpty();
String suggestionString;
if (suggestions.size() == 1) {
suggestionString = suggestions.get(0);
} else if (suggestions.size() == 2) {
suggestionString = String.format("%1$s or %2$s",
suggestions.get(0), suggestions.get(1));
} else {
suggestionString = LintUtils.formatList(suggestions, -1);
}
String message = String.format("Misspelled tag `<%1$s>`: Did you mean `%2$s` ?",
tag, suggestionString);
context.report(ISSUE, element, context.getLocation(element),
message);
}
}
}
}