001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2026 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.javadoc;
021
022import java.util.ArrayList;
023import java.util.List;
024import java.util.regex.Pattern;
025
026import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
027import com.puppycrawl.tools.checkstyle.PropertyType;
028import com.puppycrawl.tools.checkstyle.XdocsPropertyType;
029import com.puppycrawl.tools.checkstyle.api.DetailNode;
030import com.puppycrawl.tools.checkstyle.api.JavadocCommentsTokenTypes;
031import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
032import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
033
034/**
035 * <div>
036 * Checks that Javadoc comments do not match a configured regular expression.
037 * </div>
038 *
039 * <p>
040 * The check can operate on raw Javadoc source or on the text visible to readers.
041 * Raw matching includes Javadoc tags, HTML tags, HTML attributes, and formatting
042 * characters. Visible-text matching ignores markup and matches only Javadoc text content.
043 * </p>
044 *
045 * @since 13.10.0
046 */
047@FileStatefulCheck
048public class JavadocRegexpCheck extends AbstractJavadocCheck {
049
050    /**
051     * A key is pointing to the warning message text in "messages.properties" file.
052     */
053    public static final String MSG_JAVADOC_REGEXP = "javadoc.regexp.match";
054
055    /**
056     * Text fragments visible to readers of the current Javadoc comment.
057     */
058    private final List<String> visibleText = new ArrayList<>();
059
060    /**
061     * Specify the regular expression to match forbidden Javadoc content.
062     */
063    @XdocsPropertyType(PropertyType.PATTERN)
064    private String format = "^$";
065
066    /**
067     * Control whether to ignore case when matching.
068     */
069    private boolean ignoreCase;
070
071    /**
072     * Control whether to ignore Javadoc and HTML markup when matching.
073     */
074    private boolean ignoreMarkup;
075
076    /**
077     * Line number of the previous collected text node.
078     */
079    private int previousTextLineNumber;
080
081    /**
082     * Creates a new {@code JavadocRegexpCheck} instance.
083     */
084    public JavadocRegexpCheck() {
085        // no code by default
086    }
087
088    /**
089     * Setter to specify the regular expression to match forbidden Javadoc content.
090     *
091     * @param format regular expression to match forbidden Javadoc content.
092     * @since 13.10.0
093     */
094    public void setFormat(String format) {
095        this.format = format;
096    }
097
098    /**
099     * Setter to control whether to ignore case when matching.
100     *
101     * @param ignoreCase whether to ignore case when matching.
102     * @since 13.10.0
103     */
104    public void setIgnoreCase(boolean ignoreCase) {
105        this.ignoreCase = ignoreCase;
106    }
107
108    /**
109     * Setter to control whether to ignore Javadoc and HTML markup when matching.
110     *
111     * @param ignoreMarkup whether to ignore Javadoc and HTML markup when matching.
112     * @since 13.10.0
113     */
114    public void setIgnoreMarkup(boolean ignoreMarkup) {
115        this.ignoreMarkup = ignoreMarkup;
116    }
117
118    @Override
119    public int[] getRequiredJavadocTokens() {
120        return new int[] {
121            JavadocCommentsTokenTypes.TEXT,
122        };
123    }
124
125    @Override
126    public int[] getDefaultJavadocTokens() {
127        return getRequiredJavadocTokens();
128    }
129
130    @Override
131    public void beginJavadocTree(DetailNode rootAst) {
132        visibleText.clear();
133    }
134
135    @Override
136    public void visitJavadocToken(DetailNode ast) {
137        if (!isIgnoredText(ast)) {
138            appendVisibleText(ast);
139        }
140    }
141
142    @Override
143    public void finishJavadocTree(DetailNode rootAst) {
144        final String content;
145        if (ignoreMarkup) {
146            content = String.join("", visibleText);
147        }
148        else {
149            content = JavadocUtil.getJavadocCommentContent(getBlockCommentAst());
150        }
151
152        final Pattern regexp = createRegexp();
153        if (regexp.matcher(content).find()) {
154            log(rootAst.getLineNumber(), MSG_JAVADOC_REGEXP, regexp.pattern());
155        }
156    }
157
158    /**
159     * Creates the regexp based on {@link #format} and {@link #ignoreCase}.
160     *
161     * @return pattern used to find forbidden content.
162     */
163    private Pattern createRegexp() {
164        final int compileFlags;
165        if (ignoreCase) {
166            compileFlags = Pattern.CASE_INSENSITIVE;
167        }
168        else {
169            compileFlags = 0;
170        }
171        return CommonUtil.createPattern(format, compileFlags);
172    }
173
174    /**
175     * Appends visible text from a Javadoc text node.
176     *
177     * @param textNode text node to append.
178     */
179    private void appendVisibleText(DetailNode textNode) {
180        if (!visibleText.isEmpty() && previousTextLineNumber < textNode.getLineNumber()) {
181            visibleText.add(" ");
182        }
183        visibleText.add(textNode.getText());
184        previousTextLineNumber = textNode.getLineNumber();
185    }
186
187    /**
188     * Checks whether a text node belongs to a non-rendered HTML comment.
189     *
190     * @param textNode text node to check.
191     * @return {@code true} if the text node should be ignored.
192     */
193    private static boolean isIgnoredText(DetailNode textNode) {
194        boolean result = false;
195        DetailNode current = textNode;
196
197        while (current != null) {
198            if (current.getType() == JavadocCommentsTokenTypes.HTML_COMMENT) {
199                result = true;
200                break;
201            }
202            current = current.getParent();
203        }
204
205        return result;
206    }
207
208}