View Javadoc
1   /*
2    * SPDX-FileCopyrightText: Copyright (c) 2011-2026 Yegor Bugayenko
3    * SPDX-License-Identifier: MIT
4    */
5   package com.qulice.errorprone;
6   
7   import com.qulice.spi.Violation;
8   import java.util.ArrayList;
9   import java.util.Collection;
10  import java.util.List;
11  import java.util.Optional;
12  import java.util.regex.Matcher;
13  import java.util.regex.Pattern;
14  
15  /**
16   * What a forked {@code javac} printed, read as violations.
17   *
18   * <p>Every diagnostic counts, not just the ones ErrorProne labels with
19   * its {@code [CheckName]} prefix: a plain {@code cannot find symbol} is
20   * the loudest signal of all, because {@code javac} stops before the
21   * analysis phase once it sees an error and ErrorProne then reports
22   * nothing at all. Lines that carry no source position, such as
23   * {@code error: warnings found and -Werror specified}, are blamed on the
24   * project itself.</p>
25   *
26   * @since 1.0
27   */
28  final class Diagnostics {
29  
30      /**
31       * Standard {@code javac} diagnostic format,
32       * {@code path:line: warning|error: body}, where the body optionally
33       * opens with the {@code [Name]} prefix that ErrorProne puts on its
34       * findings and {@code javac} puts on its lint categories.
35       */
36      private static final Pattern POSITIONED = Pattern.compile(
37          "^(.+?):(\\d+): (?:warning|error): (?:\\[([A-Za-z][A-Za-z0-9_]*)] )?(.+)$"
38      );
39  
40      /**
41       * A {@code javac} diagnostic that names no source position.
42       */
43      private static final Pattern GLOBAL = Pattern.compile(
44          "^(?:warning|error): (?:\\[([A-Za-z][A-Za-z0-9_]*)] )?(.+)$"
45      );
46  
47      /**
48       * Name of the validator to attribute the violations to.
49       */
50      private final String validator;
51  
52      /**
53       * File to blame for the diagnostics that name no source position.
54       */
55      private final String fallback;
56  
57      /**
58       * Constructor.
59       *
60       * @param validator Name of the validator reporting these diagnostics
61       * @param fallback File to blame when a diagnostic names no position
62       */
63      Diagnostics(final String validator, final String fallback) {
64          this.validator = validator;
65          this.fallback = fallback;
66      }
67  
68      /**
69       * Read the diagnostics out of the compiler's output.
70       *
71       * @param output Combined stdout/stderr of the forked process
72       * @return Violations, one per diagnostic line
73       */
74      Collection<Violation> violations(final List<String> output) {
75          final Collection<Violation> violations = new ArrayList<>(0);
76          for (final String line : output) {
77              final Matcher positioned = Diagnostics.POSITIONED.matcher(line);
78              final Matcher global = Diagnostics.GLOBAL.matcher(line);
79              if (positioned.matches()) {
80                  violations.add(
81                      this.violation(
82                          positioned.group(1),
83                          positioned.group(2),
84                          Diagnostics.check(positioned.group(3)),
85                          positioned.group(4)
86                      )
87                  );
88              } else if (global.matches()) {
89                  violations.add(
90                      this.violation(
91                          this.fallback,
92                          "0",
93                          Diagnostics.check(global.group(1)),
94                          global.group(2)
95                      )
96                  );
97              }
98          }
99          return violations;
100     }
101 
102     private Violation violation(final String file, final String lines,
103         final String check, final String message) {
104         return new Violation.Default(
105             this.validator,
106             check,
107             file,
108             lines,
109             String.format("[%s] %s", check, message)
110         );
111     }
112 
113     private static String check(final String name) {
114         return Optional.ofNullable(name).orElse("javac");
115     }
116 }