aboutsummaryrefslogtreecommitdiffstats
path: root/lint/libs/lint_checks/src/com/android/tools/lint/checks/TypographyDetector.java
diff options
context:
space:
mode:
Diffstat (limited to 'lint/libs/lint_checks/src/com/android/tools/lint/checks/TypographyDetector.java')
-rw-r--r--lint/libs/lint_checks/src/com/android/tools/lint/checks/TypographyDetector.java499
1 files changed, 0 insertions, 499 deletions
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/TypographyDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/TypographyDetector.java
deleted file mode 100644
index 66d6889..0000000
--- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/TypographyDetector.java
+++ /dev/null
@@ -1,499 +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.checks;
-
-import static com.android.SdkConstants.TAG_STRING;
-import static com.android.SdkConstants.TAG_STRING_ARRAY;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.VisibleForTesting;
-import com.android.resources.ResourceFolderType;
-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.ResourceXmlDetector;
-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 org.w3c.dom.Element;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Checks for various typographical issues in string definitions.
- */
-public class TypographyDetector extends ResourceXmlDetector {
- /** Replace hyphens with dashes? */
- public static final Issue DASHES = Issue.create(
- "TypographyDashes", //$NON-NLS-1$
- "Looks for usages of hyphens which can be replaced by n dash and m dash characters",
- "The \"n dash\" (\u2013, –) and the \"m dash\" (\u2014, —) " +
- "characters are used for ranges (n dash) and breaks (m dash). Using these " +
- "instead of plain hyphens can make text easier to read and your application " +
- "will look more polished.",
- Category.TYPOGRAPHY,
- 5,
- Severity.WARNING,
- TypographyDetector.class,
- Scope.RESOURCE_FILE_SCOPE).
- setMoreInfo("http://en.wikipedia.org/wiki/Dash"); //$NON-NLS-1$
-
- /** Replace dumb quotes with smart quotes? */
- public static final Issue QUOTES = Issue.create(
- "TypographyQuotes", //$NON-NLS-1$
- "Looks for straight quotes which can be replaced by curvy quotes",
- "Straight single quotes and double quotes, when used as a pair, can be replaced " +
- "by \"curvy quotes\" (or directional quotes). This can make the text more " +
- "readable.\n" +
- "\n" +
- "Note that you should never use grave accents and apostrophes to quote, " +
- "`like this'.\n" +
- "\n" +
- "(Also note that you should not use curvy quotes for code fragments.)",
- Category.TYPOGRAPHY,
- 5,
- Severity.WARNING,
- TypographyDetector.class,
- Scope.RESOURCE_FILE_SCOPE).
- setMoreInfo("http://en.wikipedia.org/wiki/Quotation_mark"). //$NON-NLS-1$
- // This feature is apparently controversial: recent apps have started using
- // straight quotes to avoid inconsistencies. Disabled by default for now.
- setEnabledByDefault(false);
-
- /** Replace fraction strings with fraction characters? */
- public static final Issue FRACTIONS = Issue.create(
- "TypographyFractions", //$NON-NLS-1$
- "Looks for fraction strings which can be replaced with a fraction character",
- "You can replace certain strings, such as 1/2, and 1/4, with dedicated " +
- "characters for these, such as \u00BD (½) and \00BC (¼). " +
- "This can help make the text more readable.",
- Category.TYPOGRAPHY,
- 5,
- Severity.WARNING,
- TypographyDetector.class,
- Scope.RESOURCE_FILE_SCOPE).
- setMoreInfo("http://en.wikipedia.org/wiki/Number_Forms"); //$NON-NLS-1$
-
- /** Replace ... with the ellipsis character? */
- public static final Issue ELLIPSIS = Issue.create(
- "TypographyEllipsis", //$NON-NLS-1$
- "Looks for ellipsis strings (...) which can be replaced with an ellipsis character",
- "You can replace the string \"...\" with a dedicated ellipsis character, " +
- "ellipsis character (\u2026, …). This can help make the text more readable.",
- Category.TYPOGRAPHY,
- 5,
- Severity.WARNING,
- TypographyDetector.class,
- Scope.RESOURCE_FILE_SCOPE).
- setMoreInfo("http://en.wikipedia.org/wiki/Ellipsis"); //$NON-NLS-1$
-
- /** The main issue discovered by this detector */
- public static final Issue OTHER = Issue.create(
- "TypographyOther", //$NON-NLS-1$
- "Looks for miscellaneous typographical problems like replacing (c) with \u00A9",
- "This check looks for miscellaneous typographical problems and offers replacement " +
- "sequences that will make the text easier to read and your application more " +
- "polished.",
- Category.TYPOGRAPHY,
- 3,
- Severity.WARNING,
- TypographyDetector.class,
- Scope.RESOURCE_FILE_SCOPE);
-
- private static final String GRAVE_QUOTE_MESSAGE =
- "Avoid quoting with grave accents; use apostrophes or better yet directional quotes instead";
- private static final String ELLIPSIS_MESSAGE =
- "Replace \"...\" with ellipsis character (\u2026, …) ?";
- private static final String EN_DASH_MESSAGE =
- "Replace \"-\" with an \"en dash\" character (\u2013, –) ?";
- private static final String EM_DASH_MESSAGE =
- "Replace \"--\" with an \"em dash\" character (\u2014, —) ?";
- private static final String TYPOGRAPHIC_APOSTROPHE_MESSAGE =
- "Replace apostrophe (') with typographic apostrophe (\u2019, ’) ?";
- private static final String SINGLE_QUOTE_MESSAGE =
- "Replace straight quotes ('') with directional quotes (\u2018\u2019, ‘ and ’) ?";
- private static final String DBL_QUOTES_MESSAGE =
- "Replace straight quotes (\") with directional quotes (\u201C\u201D, “ and ”) ?";
- private static final String COPYRIGHT_MESSAGE =
- "Replace (c) with copyright symbol \u00A9 (©) ?";
-
- /**
- * Pattern used to detect scenarios which can be replaced with n dashes: a
- * numeric range with a hyphen in the middle (and possibly spaces)
- */
- @VisibleForTesting
- static final Pattern HYPHEN_RANGE_PATTERN =
- Pattern.compile(".*(\\d+\\s*)-(\\s*\\d+).*"); //$NON-NLS-1$
-
- /**
- * Pattern used to detect scenarios where a grave accent mark is used
- * to do ASCII quotations of the form `this'' or ``this'', which is frowned upon.
- * This pattern tries to avoid falsely complaining about strings like
- * "Type Option-` then 'Escape'."
- */
- @VisibleForTesting
- static final Pattern GRAVE_QUOTATION =
- Pattern.compile("(^[^`]*`[^'`]+'[^']*$)|(^[^`]*``[^'`]+''[^']*$)"); //$NON-NLS-1$
-
- /**
- * Pattern used to detect common fractions, e.g. 1/2, 1/3, 2/3, 1/4, 3/4 and
- * variations like 2 / 3, but not 11/22 and so on.
- */
- @VisibleForTesting
- static final Pattern FRACTION_PATTERN =
- Pattern.compile(".*\\b([13])\\s*/\\s*([234])\\b.*"); //$NON-NLS-1$
-
- /**
- * Pattern used to detect single quote strings, such as 'hello', but
- * not just quoted strings like 'Double quote: "', and not sentences
- * where there are multiple apostrophes but not in a quoting context such
- * as "Mind Your P's and Q's".
- */
- @VisibleForTesting
- static final Pattern SINGLE_QUOTE =
- Pattern.compile(".*\\W*'[^']+'(\\W.*)?"); //$NON-NLS-1$
-
- private static final String FRACTION_MESSAGE =
- "Use fraction character %1$c (%2$s) instead of %3$s ?";
-
- private static final String FRACTION_MESSAGE_PATTERN =
- "Use fraction character (.+) \\((.+)\\) instead of (.+) \\?";
-
- private boolean mCheckDashes;
- private boolean mCheckQuotes;
- private boolean mCheckFractions;
- private boolean mCheckEllipsis;
- private boolean mCheckMisc;
-
- /** Constructs a new {@link TypographyDetector} */
- public TypographyDetector() {
- }
-
- @Override
- public boolean appliesTo(@NonNull ResourceFolderType folderType) {
- return folderType == ResourceFolderType.VALUES;
- }
-
- @Override
- public @NonNull Speed getSpeed() {
- return Speed.FAST;
- }
-
- @Override
- public Collection<String> getApplicableElements() {
- return Arrays.asList(
- TAG_STRING,
- TAG_STRING_ARRAY
- );
- }
-
- @Override
- public void beforeCheckProject(@NonNull Context context) {
- mCheckDashes = context.isEnabled(DASHES);
- mCheckQuotes = context.isEnabled(QUOTES);
- mCheckFractions = context.isEnabled(FRACTIONS);
- mCheckEllipsis = context.isEnabled(ELLIPSIS);
- mCheckMisc = context.isEnabled(OTHER);
- }
-
- @Override
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- NodeList childNodes = element.getChildNodes();
- for (int i = 0, n = childNodes.getLength(); i < n; i++) {
- Node child = childNodes.item(i);
- if (child.getNodeType() == Node.TEXT_NODE) {
- String text = child.getNodeValue();
- checkText(context, element, child, text);
- } else if (child.getNodeType() == Node.ELEMENT_NODE &&
- child.getParentNode().getNodeName().equals(TAG_STRING_ARRAY)) {
- // String array item children
- NodeList items = child.getChildNodes();
- for (int j = 0, m = items.getLength(); j < m; j++) {
- Node item = items.item(j);
- if (item.getNodeType() == Node.TEXT_NODE) {
- String text = item.getNodeValue();
- checkText(context, child, item, text);
- }
- }
- }
- }
- }
-
- private void checkText(XmlContext context, Node element, Node textNode, String text) {
- if (mCheckEllipsis) {
- // Replace ... with ellipsis character?
- int ellipsis = text.indexOf("..."); //$NON-NLS-1$
- if (ellipsis != -1 && !text.startsWith(".", ellipsis + 3)) { //$NON-NLS-1$
- context.report(ELLIPSIS, element, context.getLocation(textNode),
- ELLIPSIS_MESSAGE, null);
- }
- }
-
- // Dashes
- if (mCheckDashes) {
- int hyphen = text.indexOf('-');
- if (hyphen != -1) {
- // n dash
- Matcher matcher = HYPHEN_RANGE_PATTERN.matcher(text);
- if (matcher.matches()) {
- // Make sure that if there is no space before digit there isn't
- // one on the left either -- since we don't want to consider
- // "1 2 -3" as a range from 2 to 3
- boolean isNegativeNumber =
- !Character.isWhitespace(matcher.group(2).charAt(0)) &&
- Character.isWhitespace(matcher.group(1).charAt(
- matcher.group(1).length() - 1));
- if (!isNegativeNumber) {
- context.report(DASHES, element, context.getLocation(textNode),
- EN_DASH_MESSAGE,
- null);
- }
- }
-
- // m dash
- int emdash = text.indexOf("--"); //$NON-NLS-1$
- // Don't suggest replacing -- or "--" with an m dash since these are sometimes
- // used as digit marker strings
- if (emdash > 1 && !text.startsWith("-", emdash + 2)) { //$NON-NLS-1$
- context.report(DASHES, element, context.getLocation(textNode),
- EM_DASH_MESSAGE, null);
- }
- }
- }
-
- if (mCheckQuotes) {
- // Check for single quotes that can be replaced with directional quotes
- int quoteStart = text.indexOf('\'');
- if (quoteStart != -1) {
- int quoteEnd = text.indexOf('\'', quoteStart + 1);
- if (quoteEnd != -1 && quoteEnd > quoteStart + 1
- && (quoteEnd < text.length() -1 || quoteStart > 0)
- && SINGLE_QUOTE.matcher(text).matches()) {
- context.report(QUOTES, element, context.getLocation(textNode),
- SINGLE_QUOTE_MESSAGE, null);
- return;
- }
-
- // Check for apostrophes that can be replaced by typographic apostrophes
- if (quoteEnd == -1 && quoteStart > 0
- && Character.isLetterOrDigit(text.charAt(quoteStart - 1))) {
- context.report(QUOTES, element, context.getLocation(textNode),
- TYPOGRAPHIC_APOSTROPHE_MESSAGE, null);
- return;
- }
- }
-
- // Check for double quotes that can be replaced by directional double quotes
- quoteStart = text.indexOf('"');
- if (quoteStart != -1) {
- int quoteEnd = text.indexOf('"', quoteStart + 1);
- if (quoteEnd != -1 && quoteEnd > quoteStart + 1) {
- if (quoteEnd < text.length() -1 || quoteStart > 0) {
- context.report(QUOTES, element, context.getLocation(textNode),
- DBL_QUOTES_MESSAGE, null);
- return;
- }
- }
- }
-
- // Check for grave accent quotations
- if (text.indexOf('`') != -1 && GRAVE_QUOTATION.matcher(text).matches()) {
- // Are we indenting ``like this'' or `this' ? If so, complain
- context.report(QUOTES, element, context.getLocation(textNode),
- GRAVE_QUOTE_MESSAGE, null);
- return;
- }
-
- // Consider suggesting other types of directional quotes, such as guillemets, in
- // other languages?
- // There are a lot of exceptions and special cases to be considered so
- // this will need careful implementation and testing.
- // See http://en.wikipedia.org/wiki/Non-English_usage_of_quotation_marks
- }
-
- // Fraction symbols?
- if (mCheckFractions && text.indexOf('/') != -1) {
- Matcher matcher = FRACTION_PATTERN.matcher(text);
- if (matcher.matches()) {
- String top = matcher.group(1); // Numerator
- String bottom = matcher.group(2); // Denominator
- if (top.equals("1") && bottom.equals("2")) { //$NON-NLS-1$ //$NON-NLS-2$
- context.report(FRACTIONS, element, context.getLocation(textNode),
- String.format(FRACTION_MESSAGE, '\u00BD', "&#189;", "1/2"), null);
- } else if (top.equals("1") && bottom.equals("4")) { //$NON-NLS-1$ //$NON-NLS-2$
- context.report(FRACTIONS, element, context.getLocation(textNode),
- String.format(FRACTION_MESSAGE, '\u00BC', "&#188;", "1/4"), null);
- } else if (top.equals("3") && bottom.equals("4")) { //$NON-NLS-1$ //$NON-NLS-2$
- context.report(FRACTIONS, element, context.getLocation(textNode),
- String.format(FRACTION_MESSAGE, '\u00BE', "&#190;", "3/4"), null);
- } else if (top.equals("1") && bottom.equals("3")) { //$NON-NLS-1$ //$NON-NLS-2$
- context.report(FRACTIONS, element, context.getLocation(textNode),
- String.format(FRACTION_MESSAGE, '\u2153', "&#8531;", "1/3"), null);
- } else if (top.equals("2") && bottom.equals("3")) { //$NON-NLS-1$ //$NON-NLS-2$
- context.report(FRACTIONS, element, context.getLocation(textNode),
- String.format(FRACTION_MESSAGE, '\u2154', "&#8532;", "2/3"), null);
- }
- }
- }
-
- if (mCheckMisc) {
- // Fix copyright symbol?
- if (text.indexOf('(') != -1
- && (text.contains("(c)") || text.contains("(C)"))) { //$NON-NLS-1$ //$NON-NLS-2$
- // Suggest replacing with copyright symbol?
- context.report(OTHER, element, context.getLocation(textNode),
- COPYRIGHT_MESSAGE, null);
- // Replace (R) and TM as well? There are unicode characters for these but they
- // are probably not very common within Android app strings.
- }
- }
- }
-
- /**
- * An object describing a single edit to be made. The offset points to a
- * location to start editing; the length is the number of characters to
- * delete, and the replaceWith string points to a string to insert at the
- * offset. Note that this can model not just replacement edits but deletions
- * (empty replaceWith) and insertions (replace length = 0) too.
- */
- public static class ReplaceEdit {
- /** The offset of the edit */
- public final int offset;
- /** The number of characters to delete at the offset */
- public final int length;
- /** The characters to insert at the offset */
- public final String replaceWith;
-
- /**
- * Creates a new replace edit
- *
- * @param offset the offset of the edit
- * @param length the number of characters to delete at the offset
- * @param replaceWith the characters to insert at the offset
- */
- public ReplaceEdit(int offset, int length, String replaceWith) {
- super();
- this.offset = offset;
- this.length = length;
- this.replaceWith = replaceWith;
- }
- }
-
- /**
- * Returns a list of edits to be applied to fix the suggestion made by the
- * given warning. The specific issue id and message should be the message
- * provided by this detector in an earlier run.
- * <p>
- * This is intended to help tools implement automatic fixes of these
- * warnings. The reason only the message and issue id can be provided
- * instead of actual state passed in the data field to a reporter is that
- * fix operation can be run much later than the lint is processed (for
- * example, in a subsequent run of the IDE when only the warnings have been
- * persisted),
- *
- * @param issueId the issue id, which should be the id for one of the
- * typography issues
- * @param message the actual error message, which should be a message
- * provided by this detector
- * @param textNode a text node which corresponds to the text node the
- * warning operated on
- * @return a list of edits, which is never null but could be empty. The
- * offsets in the edit objects are relative to the text node.
- */
- public static List<ReplaceEdit> getEdits(String issueId, String message, Node textNode) {
- List<ReplaceEdit> edits = new ArrayList<ReplaceEdit>();
- String text = textNode.getNodeValue();
- if (message.equals(ELLIPSIS_MESSAGE)) {
- int offset = text.indexOf("..."); //$NON-NLS-1$
- if (offset != -1) {
- edits.add(new ReplaceEdit(offset, 3, "\u2026")); //$NON-NLS-1$
- }
- } else if (message.equals(EN_DASH_MESSAGE)) {
- int offset = text.indexOf('-');
- if (offset != -1) {
- edits.add(new ReplaceEdit(offset, 1, "\u2013")); //$NON-NLS-1$
- }
- } else if (message.equals(EM_DASH_MESSAGE)) {
- int offset = text.indexOf("--"); //$NON-NLS-1$
- if (offset != -1) {
- edits.add(new ReplaceEdit(offset, 2, "\u2014")); //$NON-NLS-1$
- }
- } else if (message.equals(TYPOGRAPHIC_APOSTROPHE_MESSAGE)) {
- int offset = text.indexOf('\'');
- if (offset != -1) {
- edits.add(new ReplaceEdit(offset, 1, "\u2019")); //$NON-NLS-1$
- }
- } else if (message.equals(COPYRIGHT_MESSAGE)) {
- int offset = text.indexOf("(c)"); //$NON-NLS-1$
- if (offset == -1) {
- offset = text.indexOf("(C)"); //$NON-NLS-1$
- }
- if (offset != -1) {
- edits.add(new ReplaceEdit(offset, 3, "\u00A9")); //$NON-NLS-1$
- }
- } else if (message.equals(SINGLE_QUOTE_MESSAGE)) {
- int offset = text.indexOf('\'');
- if (offset != -1) {
- int endOffset = text.indexOf("'", offset + 1); //$NON-NLS-1$
- if (endOffset != -1) {
- edits.add(new ReplaceEdit(offset, 1, "\u2018")); //$NON-NLS-1$
- edits.add(new ReplaceEdit(endOffset, 1, "\u2019")); //$NON-NLS-1$
- }
- }
- } else if (message.equals(DBL_QUOTES_MESSAGE)) {
- int offset = text.indexOf('"');
- if (offset != -1) {
- int endOffset = text.indexOf('"', offset + 1);
- if (endOffset != -1) {
- edits.add(new ReplaceEdit(offset, 1, "\u201C")); //$NON-NLS-1$
- edits.add(new ReplaceEdit(endOffset, 1, "\u201D")); //$NON-NLS-1$
- }
- }
- } else if (message.equals(GRAVE_QUOTE_MESSAGE)) {
- int offset = text.indexOf('`');
- if (offset != -1) {
- int endOffset = text.indexOf('\'', offset + 1);
- if (endOffset != -1) {
- edits.add(new ReplaceEdit(offset, 1, "\u2018")); //$NON-NLS-1$
- edits.add(new ReplaceEdit(endOffset, 1, "\u2019")); //$NON-NLS-1$
- }
- }
- } else {
- Matcher matcher = Pattern.compile(FRACTION_MESSAGE_PATTERN).matcher(message);
- if (matcher.find()) {
- // "Use fraction character %1$c (%2$s) instead of %3$s ?";
- String replace = matcher.group(3);
- int offset = text.indexOf(replace);
- if (offset != -1) {
- String replaceWith = matcher.group(2);
- edits.add(new ReplaceEdit(offset, replace.length(), replaceWith));
- }
- }
- }
-
- return edits;
- }
-}