diff options
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.java | 499 |
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', "½", "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', "¼", "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', "¾", "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', "⅓", "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', "⅔", "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; - } -} |