001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2022 the original author or authors.
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018///////////////////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle.checks.whitespace;
021
022import java.io.File;
023
024import com.puppycrawl.tools.checkstyle.StatelessCheck;
025import com.puppycrawl.tools.checkstyle.api.AbstractFileSetCheck;
026import com.puppycrawl.tools.checkstyle.api.FileText;
027
028/**
029 * <p>
030 * Checks that there are no tab characters ({@code '\t'}) in the source code.
031 * </p>
032 * <p>
033 * Rationale:
034 * </p>
035 * <ul>
036 * <li>
037 * Developers should not need to configure the tab width of their text editors in order
038 * to be able to read source code.
039 * </li>
040 * <li>
041 * From the Apache jakarta coding standards: In a distributed development environment,
042 * when the commit messages get sent to a mailing list, they are almost impossible to
043 * read if you use tabs.
044 * </li>
045 * </ul>
046 * <ul>
047 * <li>
048 * Property {@code eachLine} - Control whether to report on each line containing a tab,
049 * or just the first instance.
050 * Type is {@code boolean}.
051 * Default value is {@code false}.
052 * </li>
053 * <li>
054 * Property {@code fileExtensions} - Specify file type extension of files to process.
055 * Type is {@code java.lang.String[]}.
056 * Default value is {@code ""}.
057 * </li>
058 * </ul>
059 * <p>
060 * To configure the check to report only the first instance in each file:
061 * </p>
062 * <pre>
063 * &lt;module name=&quot;FileTabCharacter&quot;/&gt;
064 * </pre>
065 * <p>
066 * Example - Test.java:
067 * </p>
068 * <pre>
069 * public class Test {
070 *   int a;     // violation, indented using tab
071 *
072 *   public void foo (int arg) { // OK, indented using tab, only first occurrence in file reported
073 *     a = arg;                  // OK, indented using spaces
074 *   }                           // OK, indented using spaces
075 * }
076 * </pre>
077 * <p>
078 * To configure the check to report each instance in each file:
079 * </p>
080 * <pre>
081 * &lt;module name=&quot;FileTabCharacter&quot;&gt;
082 *   &lt;property name=&quot;eachLine&quot; value=&quot;true&quot;/&gt;
083 * &lt;/module&gt;
084 * </pre>
085 * <p>
086 * Example - Test.java:
087 * </p>
088 * <pre>
089 * public class Test {
090 *   int a;     // violation, indented using tab
091 *
092 *   public void foo (int arg) { // violation, indented using tab
093 *     a = arg;                  // OK, indented using spaces
094 *   }                           // OK, indented using spaces
095 * }
096 * </pre>
097 * <p>
098 * To configure the check to report instances on only certain file types:
099 * </p>
100 * <pre>
101 * &lt;module name=&quot;FileTabCharacter&quot;&gt;
102 *   &lt;property name=&quot;fileExtensions&quot; value=&quot;java, xml&quot;/&gt;
103 * &lt;/module&gt;
104 * </pre>
105 * <p>
106 * Example - Test.java:
107 * </p>
108 * <pre>
109 * public class Test {
110 *   int a;     // violation, indented using tab
111 *
112 *   public void foo (int arg) { // OK, indented using tab, only first occurrence in file reported
113 *     a = arg;                  // OK, indented using spaces
114 *   }                           // OK, indented using spaces
115 * }
116 * </pre>
117 * <p>
118 * Example - Test.xml:
119 * </p>
120 * <pre>
121 * &lt;?xml version="1.0" encoding="UTF-8" ?&gt;
122 * &lt;UserAccount&gt;
123 *   &lt;FirstName&gt;John&lt;/FirstName&gt; &lt;!-- violation, indented using tab --&gt;
124 *   &lt;LastName&gt;Doe&lt;/LastName&gt;    &lt;!-- only first occurrence in file reported --&gt;
125 * &lt;/UserAccount&gt;
126 * </pre>
127 * <p>
128 * Example - Test.html:
129 * </p>
130 * <pre>
131 * &lt;head&gt;
132 *   &lt;title&gt;Page Title&lt;/title&gt; &lt;!-- no check performed, html file extension --&gt;
133 * &lt;/head&gt;                     &lt;!-- not specified in check config --&gt;
134 * &lt;body&gt;
135 *   &lt;p&gt;This is a simple html document.&lt;/p&gt;
136 * &lt;/body&gt;
137 * </pre>
138 * <p>
139 * Parent is {@code com.puppycrawl.tools.checkstyle.Checker}
140 * </p>
141 * <p>
142 * Violation Message Keys:
143 * </p>
144 * <ul>
145 * <li>
146 * {@code containsTab}
147 * </li>
148 * <li>
149 * {@code file.containsTab}
150 * </li>
151 * </ul>
152 *
153 * @since 5.0
154 */
155@StatelessCheck
156public class FileTabCharacterCheck extends AbstractFileSetCheck {
157
158    /**
159     * A key is pointing to the warning message text in "messages.properties"
160     * file.
161     */
162    public static final String MSG_CONTAINS_TAB = "containsTab";
163
164    /**
165     * A key is pointing to the warning message text in "messages.properties"
166     * file.
167     */
168    public static final String MSG_FILE_CONTAINS_TAB = "file.containsTab";
169
170    /** Control whether to report on each line containing a tab, or just the first instance. */
171    private boolean eachLine;
172
173    @Override
174    protected void processFiltered(File file, FileText fileText) {
175        int lineNum = 0;
176        for (int index = 0; index < fileText.size(); index++) {
177            final String line = fileText.get(index);
178            lineNum++;
179            final int tabPosition = line.indexOf('\t');
180            if (tabPosition != -1) {
181                if (eachLine) {
182                    log(lineNum, tabPosition, MSG_CONTAINS_TAB);
183                }
184                else {
185                    log(lineNum, tabPosition, MSG_FILE_CONTAINS_TAB);
186                    break;
187                }
188            }
189        }
190    }
191
192    /**
193     * Setter to control whether to report on each line containing a tab, or just the first
194     * instance.
195     *
196     * @param eachLine Whether report on each line containing a tab.
197     */
198    public void setEachLine(boolean eachLine) {
199        this.eachLine = eachLine;
200    }
201
202}