View Javadoc
1   /*
2    * SPDX-FileCopyrightText: Copyright (c) 2011-2026 Yegor Bugayenko
3    * SPDX-License-Identifier: MIT
4    */
5   package com.qulice.checkstyle;
6   
7   import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
8   import com.puppycrawl.tools.checkstyle.api.DetailAST;
9   import com.puppycrawl.tools.checkstyle.api.TokenTypes;
10  import java.util.regex.Matcher;
11  import java.util.regex.Pattern;
12  
13  /**
14   * Prohibits hard-coded line separator escape sequences inside string literals.
15   *
16   * <p>The following constructs are prohibited, because {@code \n} and
17   * {@code \r} are OS dependent line separators:</p>
18   *
19   * <pre>
20   * String a = "first\nsecond";
21   * System.out.println("line\r\n");
22   * </pre>
23   *
24   * <p>These strings should be rewritten using
25   * {@link System#lineSeparator()} or {@link String#format(String, Object[])}
26   * with the {@code %n} directive, for example:</p>
27   *
28   * <pre>
29   * String a = "first" + System.lineSeparator() + "second";
30   * System.out.println(String.format("line%n"));
31   * </pre>
32   *
33   * @since 0.24
34   */
35  public final class ProhibitLineSeparatorInStringsCheck extends AbstractCheck {
36  
37      /**
38       * Matches one or more backslashes followed by {@code n} or {@code r};
39       * only runs with an odd backslash count denote an actual escape sequence.
40       */
41      private static final Pattern ESCAPE = Pattern.compile("\\\\+[rn]");
42  
43      /**
44       * Default constructor.
45       */
46      public ProhibitLineSeparatorInStringsCheck() {
47          // nothing to initialize
48      }
49  
50      @Override
51      public int[] getDefaultTokens() {
52          return new int[] {TokenTypes.STRING_LITERAL};
53      }
54  
55      @Override
56      public int[] getAcceptableTokens() {
57          return this.getDefaultTokens();
58      }
59  
60      @Override
61      public int[] getRequiredTokens() {
62          return this.getDefaultTokens();
63      }
64  
65      @Override
66      public void visitToken(final DetailAST ast) {
67          if (ProhibitLineSeparatorInStringsCheck.hasLineSeparator(ast.getText())) {
68              this.log(
69                  ast,
70                  "OS-dependent line separator in string literal, use System.lineSeparator() or String.format(\"%n\")"
71              );
72          }
73      }
74  
75      private static boolean hasLineSeparator(final String text) {
76          final Matcher matcher =
77              ProhibitLineSeparatorInStringsCheck.ESCAPE.matcher(text);
78          boolean found = false;
79          while (matcher.find()) {
80              if ((matcher.end() - matcher.start() - 1) % 2 == 1) {
81                  found = true;
82                  break;
83              }
84          }
85          return found;
86      }
87  }