1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package org.htmlunit;
16
17 import java.io.File;
18 import java.io.IOException;
19 import java.lang.reflect.Constructor;
20 import java.lang.reflect.Method;
21 import java.lang.reflect.Modifier;
22 import java.nio.charset.Charset;
23 import java.nio.charset.StandardCharsets;
24 import java.nio.file.Files;
25 import java.nio.file.Paths;
26 import java.time.LocalDate;
27 import java.util.ArrayList;
28 import java.util.Calendar;
29 import java.util.Iterator;
30 import java.util.List;
31 import java.util.Locale;
32 import java.util.regex.Matcher;
33 import java.util.regex.Pattern;
34
35 import org.apache.commons.io.FileUtils;
36 import org.apache.commons.lang3.StringUtils;
37 import org.apache.commons.logging.Log;
38 import org.junit.jupiter.api.AfterAll;
39 import org.junit.jupiter.api.AfterEach;
40 import org.junit.jupiter.api.Assertions;
41 import org.junit.jupiter.api.BeforeAll;
42 import org.junit.jupiter.api.BeforeEach;
43 import org.junit.jupiter.api.RepeatedTest;
44 import org.junit.jupiter.api.Test;
45 import org.junitpioneer.jupiter.RetryingTest;
46
47
48
49
50
51
52
53
54
55 public class CodeStyleTest {
56
57 private static final Charset SOURCE_ENCODING = StandardCharsets.UTF_8;
58 private static final Pattern LEADING_WHITESPACE = Pattern.compile("^\\s+");
59 private static final Pattern LOG_STATIC_STRING =
60 Pattern.compile("^\\s*LOG\\.[a-z]+\\(\"[^\"]*\"(, [a-zA-Z_]+)?\\);");
61 private List<String> failures_ = new ArrayList<>();
62 private String title_ = "unknown";
63
64
65
66
67
68 @AfterEach
69 public void after() throws IOException {
70 final StringBuilder sb = new StringBuilder();
71 for (final String error : failures_) {
72 sb.append('\n').append(error);
73 }
74
75 if (System.getenv("EXPORT_FAILURES") != null) {
76 Files.write(Paths.get("target", title_ + ".txt"), failures_);
77 }
78
79 final int errorsNumber = failures_.size();
80 if (errorsNumber == 1) {
81 Assertions.fail(title_ + " error: " + sb);
82 }
83 else if (errorsNumber > 1) {
84 Assertions.fail(title_ + " " + errorsNumber + " errors: " + sb);
85 }
86 }
87
88 private void addFailure(final String file, final int line, final String error) {
89 failures_.add(file + ", line " + (line <= 0 ? 1 : line) + ": " + error);
90 }
91
92
93
94
95 @Test
96 public void codeStyle() throws IOException {
97 title_ = "CodeStyle";
98 final List<File> files = new ArrayList<>();
99 addAll(new File("src/main"), files);
100 addAll(new File("src/test"), files);
101 final List<String> classNames = getClassNames(files);
102 process(files, classNames);
103
104
105
106
107 licenseYear();
108 versionYear();
109 parentInPom();
110 }
111
112 private static List<String> getClassNames(final List<File> files) {
113 final List<String> list = new ArrayList<>();
114 for (final File file : files) {
115 String fileName = file.getName();
116 if (fileName.endsWith(".java")) {
117 fileName = fileName.substring(0, fileName.length() - 5);
118 fileName = fileName.substring(fileName.lastIndexOf('.') + 1);
119 list.add(fileName);
120 }
121 }
122 return list;
123 }
124
125 private void addAll(final File dir, final List<File> files) throws IOException {
126 final File[] children = dir.listFiles();
127 if (children != null) {
128 for (final File child : children) {
129 if (child.isDirectory()
130 && !".git".equals(child.getName())
131 && !"brotli".equals(child.getName())
132 && !("test".equals(dir.getName()) && "resources".equals(child.getName()))) {
133 addAll(child, files);
134 }
135 else {
136 files.add(child);
137 }
138 }
139 }
140 }
141
142 private void process(final List<File> files, final List<String> classNames) throws IOException {
143 for (final File file : files) {
144 final String relativePath = file.getAbsolutePath().substring(new File(".").getAbsolutePath().length() - 1);
145 if (file.getName().endsWith(".java")) {
146 final List<String> lines = FileUtils.readLines(file, SOURCE_ENCODING);
147 openingCurlyBracket(lines, relativePath);
148 year(lines, relativePath);
149 javaDocFirstLine(lines, relativePath);
150 classJavaDoc(lines, relativePath);
151 methodFirstLine(lines, relativePath);
152 methodLastLine(lines, relativePath);
153 lineBetweenMethods(lines, relativePath);
154 runWith(lines, relativePath);
155 vs85aspx(lines, relativePath);
156 deprecated(lines, relativePath);
157 staticJSMethod(lines, relativePath);
158 singleAlert(lines, relativePath);
159 staticLoggers(lines, relativePath);
160 loggingEnabled(lines, relativePath);
161 alerts(lines, relativePath);
162 className(lines, relativePath);
163 classNameUsed(lines, classNames, relativePath);
164 spaces(lines, relativePath);
165 indentation(lines, relativePath);
166 }
167 }
168 }
169
170
171
172
173 private void openingCurlyBracket(final List<String> lines, final String path) {
174 int index = 1;
175 for (final String line : lines) {
176 if ("{".equals(line.trim())) {
177 addFailure(path, index, "Opening curly bracket is alone");
178 }
179 index++;
180 }
181 }
182
183
184
185
186 private void year(final List<String> lines, final String path) {
187 final int year = Calendar.getInstance(Locale.ROOT).get(Calendar.YEAR);
188 if (lines.size() < 2 || !lines.get(1).contains("Copyright (c) 2002-" + year)) {
189 addFailure(path, lines.size() < 2 ? 0 : 1, "Incorrect year");
190 }
191 }
192
193
194
195
196 private void javaDocFirstLine(final List<String> lines, final String relativePath) {
197 for (int index = 1; index < lines.size(); index++) {
198 final String previousLine = lines.get(index - 1);
199 final String currentLine = lines.get(index);
200 if ("/**".equals(previousLine.trim())) {
201 if ("*".equals(currentLine.trim()) || currentLine.contains("*/")) {
202 addFailure(relativePath, index + 1, "Empty first line in JavaDoc");
203 }
204 if (currentLine.trim().startsWith("*")) {
205 final String text = currentLine.trim().substring(1).trim();
206 if (!text.isEmpty() && Character.isLowerCase(text.charAt(0))) {
207 addFailure(relativePath, index + 1, "Lower case start of text in JavaDoc");
208 }
209 }
210 }
211 }
212 }
213
214
215
216
217 private void classJavaDoc(final List<String> lines, final String relativePath) {
218 for (int index = 1; index < lines.size(); index++) {
219 final String previousLine = lines.get(index - 1);
220 final String currentLine = lines.get(index);
221 if (currentLine.startsWith("/**") && !previousLine.isEmpty()) {
222 addFailure(relativePath, index, "No empty line the beginning of JavaDoc");
223 }
224 }
225 }
226
227
228
229
230 private void methodFirstLine(final List<String> lines, final String relativePath) {
231 for (int index = 0; index < lines.size() - 1; index++) {
232 final String line = lines.get(index);
233 if (StringUtils.isBlank(lines.get(index + 1))
234 && line.length() > 4 && index > 0 && lines.get(index - 1).startsWith(" ")
235 && Character.isWhitespace(line.charAt(0)) && line.endsWith("{")
236 && !line.contains(" class ") && !line.contains(" interface ") && !line.contains(" @interface ")
237 && (!Character.isWhitespace(line.charAt(4))
238 || line.trim().startsWith("public") || line.trim().startsWith("protected")
239 || line.trim().startsWith("private"))) {
240 addFailure(relativePath, index + 2, "Empty line");
241 }
242 }
243 }
244
245
246
247
248 private void methodLastLine(final List<String> lines, final String relativePath) {
249 for (int index = 0; index < lines.size() - 1; index++) {
250 final String line = lines.get(index);
251 final String nextLine = lines.get(index + 1);
252 if (StringUtils.isBlank(line) && " }".equals(nextLine)) {
253 addFailure(relativePath, index + 1, "Empty line");
254 }
255 }
256 }
257
258
259
260
261 private void lineBetweenMethods(final List<String> lines, final String relativePath) {
262 for (int index = 0; index < lines.size() - 1; index++) {
263 final String line = lines.get(index);
264 final String nextLine = lines.get(index + 1);
265 if (" }".equals(line) && !nextLine.isEmpty() && !"}".equals(nextLine)) {
266 addFailure(relativePath, index + 1, "Non-empty line");
267 }
268 if (nextLine.trim().equals("/**") && line.trim().equals("}")) {
269 addFailure(relativePath, index + 2, "Non-empty line");
270 }
271 }
272 }
273
274
275
276
277 @Test
278 public void xmlStyle() throws Exception {
279 title_ = "XMLStyle";
280 processXML(new File("."), false);
281 processXML(new File("src/main/resources"), true);
282 processXML(new File("src/assembly"), true);
283 processXML(new File("src/changes"), true);
284 }
285
286 private void processXML(final File dir, final boolean recursive) throws Exception {
287 final File[] files = dir.listFiles();
288 if (files != null) {
289 for (final File file : files) {
290 if (file.isDirectory() && !".git".equals(file.getName())) {
291 if (recursive) {
292 processXML(file, true);
293 }
294 }
295 else {
296 if (file.getName().endsWith(".xml")) {
297 final List<String> lines = FileUtils.readLines(file, SOURCE_ENCODING);
298 final String relativePath = file.getAbsolutePath().substring(
299 new File(".").getAbsolutePath().length() - 1);
300 mixedIndentation(lines, relativePath);
301 trailingWhitespace(lines, relativePath);
302 badIndentationLevels(lines, relativePath);
303 }
304 }
305 }
306 }
307 }
308
309
310
311
312 private void mixedIndentation(final List<String> lines, final String relativePath) {
313 for (int i = 0; i < lines.size(); i++) {
314 final String line = lines.get(i);
315 if (line.indexOf('\t') != -1) {
316 addFailure(relativePath, i + 1, "Mixed indentation");
317 }
318 }
319 }
320
321
322
323
324 private void trailingWhitespace(final List<String> lines, final String relativePath) {
325 for (int i = 0; i < lines.size(); i++) {
326 final String line = lines.get(i);
327 if (!line.isEmpty()) {
328 final char last = line.charAt(line.length() - 1);
329 if (Character.isWhitespace(last)) {
330 addFailure(relativePath, i + 1, "Trailing whitespace");
331 }
332 }
333 }
334 }
335
336
337
338
339 private void badIndentationLevels(final List<String> lines, final String relativePath) {
340 for (int i = 0; i < lines.size(); i++) {
341 final int indentation = getIndentation(lines.get(i));
342 if (indentation % 4 != 0) {
343 addFailure(relativePath, i + 1, "Bad indentation level (" + indentation + ")");
344 }
345 }
346 }
347
348
349
350
351 private void licenseYear() throws IOException {
352 final List<String> lines = FileUtils.readLines(new File("checkstyle.xml"), SOURCE_ENCODING);
353 boolean check = false;
354 final String copyright = "Copyright (c) 2002-" + LocalDate.now().getYear();
355 for (final String line : lines) {
356 if (line.contains("<property name=\"header\"")) {
357 if (!line.contains(copyright)) {
358 addFailure("checkstyle.xml", 0, "Incorrect year in checkstyle.xml");
359 }
360 check = true;
361 }
362 }
363 if (!check) {
364 addFailure("checkstyle.xml", 0, "No \"header\" found");
365 }
366 }
367
368
369
370
371 private void versionYear() throws IOException {
372 final List<String> lines =
373 FileUtils.readLines(new File("src/main/java/org/htmlunit/Version.java"),
374 SOURCE_ENCODING);
375 for (final String line : lines) {
376 if (line.contains("return \"Copyright (c) 2002-" + Calendar.getInstance(Locale.ROOT).get(Calendar.YEAR))) {
377 return;
378 }
379 }
380 addFailure("src/main/java/org/htmlunit/Version.java", 0, "Incorrect year in Version.getCopyright()");
381 }
382
383
384
385
386 private void parentInPom() throws IOException {
387 final List<String> lines = FileUtils.readLines(new File("pom.xml"), SOURCE_ENCODING);
388 for (int i = 0; i < lines.size(); i++) {
389 if (lines.get(i).contains("<parent>")) {
390 addFailure("pom.xml", i + 1, "'pom.xml' should not have <parent> tag");
391 break;
392 }
393 }
394 }
395
396
397
398
399 private void runWith(final List<String> lines, final String relativePath) {
400 if (relativePath.replace('\\', '/').contains("src/test/java")
401 && !relativePath.contains("CodeStyleTest")
402 && !relativePath.contains("WebClient9Test")
403 && !relativePath.contains("FaqTest")) {
404 boolean runWith = false;
405 boolean browserNone = true;
406 int index = 1;
407 for (final String line : lines) {
408 if (line.contains("@RunWith(BrowserRunner.class)")) {
409 runWith = true;
410 }
411 if (line.contains("@Test")) {
412 browserNone = false;
413 }
414 if (relativePath.contains("JavaScriptEngineTest") && line.contains("nonStandardBrowserVersion")) {
415 browserNone = true;
416 }
417 if (runWith) {
418 if (!browserNone && line.contains("new WebClient(") && !line.contains("getBrowserVersion()")) {
419 addFailure(relativePath, index,
420 "Never directly instantiate WebClient, please use getWebClient() instead.");
421 }
422 if (line.contains("notYetImplemented()")) {
423 addFailure(relativePath, index, "Use @NotYetImplemented instead of notYetImplemented()");
424 }
425 }
426 index++;
427 }
428 }
429 }
430
431
432
433
434 private void vs85aspx(final List<String> lines, final String relativePath) {
435 if (!relativePath.contains("CodeStyleTest")) {
436 int i = 0;
437 for (final String line : lines) {
438 if (line.contains("(VS.85).aspx")) {
439 addFailure(relativePath, i + 1, "Please remove \"(VS.85)\" from the URL");
440 }
441 i++;
442 }
443 }
444 }
445
446
447
448
449 private void deprecated(final List<String> lines, final String relativePath) {
450 int i = 0;
451 for (String line : lines) {
452 line = line.trim().toLowerCase(Locale.ROOT);
453 if (line.startsWith("* @deprecated")) {
454 if (!line.startsWith("* @deprecated as of ") && !line.startsWith("* @deprecated since ")) {
455 addFailure(relativePath, i + 1,
456 "@deprecated must be immediately followed by \"As of \" or \"since \"");
457 }
458 if (!getAnnotations(lines, i).contains("@Deprecated")) {
459 addFailure(relativePath, i + 1, "No \"@Deprecated\" annotation");
460 }
461 }
462 i++;
463 }
464 }
465
466
467
468
469
470
471 private static List<String> getAnnotations(final List<String> lines, int index) {
472 final List<String> annotations = new ArrayList<>();
473 while (!lines.get(index++).trim().endsWith("*/")) {
474
475 }
476 while (lines.get(index).trim().startsWith("@")) {
477 annotations.add(lines.get(index++).trim());
478 }
479 return annotations;
480 }
481
482
483
484
485 private void staticJSMethod(final List<String> lines, final String relativePath) {
486 if (relativePath.endsWith("Console.java")) {
487 return;
488 }
489 int i = 0;
490 for (final String line : lines) {
491 if (line.contains(" static ")
492 && (line.contains(" jsxFunction_") || line.contains(" jsxGet_") || line.contains(" jsxSet_"))
493 && !line.contains(" jsxFunction_write") && !line.contains(" jsxFunction_insertBefore")
494 && !line.contains(" jsxFunction_drawImage")) {
495 addFailure(relativePath, i + 1, "Use of static JavaScript function");
496 }
497 i++;
498 }
499 }
500
501
502
503
504 private void singleAlert(final List<String> lines, final String relativePath) {
505 int i = 0;
506 for (final String line : lines) {
507 if (line.trim().startsWith("@Alerts") && line.contains("@Alerts({") && line.contains("})")) {
508 final String alert = line.substring(line.indexOf('{'), line.indexOf('}'));
509 if (!alert.contains(",") && alert.contains("\"")
510 && alert.indexOf('"', alert.indexOf('"') + 1) != -1) {
511 addFailure(relativePath, i + 1, "No need for curly brackets");
512 }
513 }
514 i++;
515 }
516 }
517
518
519
520
521 private void staticLoggers(final List<String> lines, final String relativePath) {
522 int i = 0;
523 final String logClassName = Log.class.getSimpleName();
524 for (String line : lines) {
525 line = line.trim();
526 if (line.contains(" " + logClassName + " ")
527 && !line.contains(" LOG ")
528 && !line.contains(" static ")
529 && !line.startsWith("//")
530 && !line.contains("webConsoleLogger_")
531 && !line.contains("(final Log logger)")
532 && !line.contains("httpclient.wire")) {
533 addFailure(relativePath, i + 1, "Non-static logger detected");
534 }
535 i++;
536 }
537 }
538
539
540
541
542
543
544
545
546
547
548
549
550 private void loggingEnabled(final List<String> lines, final String relativePath) {
551 if (relativePath.contains("CodeStyleTest")) {
552 return;
553 }
554 int i = 0;
555 for (final String line : lines) {
556 if (line.contains("LOG.trace(") && !LOG_STATIC_STRING.matcher(line).matches()) {
557 loggingEnabled(lines, i, "Trace", relativePath);
558 }
559 else if (line.contains("LOG.debug(") && !LOG_STATIC_STRING.matcher(line).matches()) {
560 loggingEnabled(lines, i, "Debug", relativePath);
561 }
562 i++;
563 }
564 }
565
566 private void loggingEnabled(final List<String> lines, final int index, final String method,
567 final String relativePath) {
568 final int indentation = getIndentation(lines.get(index));
569 for (int i = index - 1; i >= 0; i--) {
570 final String line = lines.get(i);
571 if (getIndentation(line) < indentation && line.contains("LOG.is" + method + "Enabled()")) {
572 return;
573 }
574 if (getIndentation(line) == 4) {
575 addFailure(relativePath, index + 1, "Must be inside a \"if (LOG.is" + method + "Enabled())\" check");
576 return;
577 }
578 }
579 }
580
581 private static int getIndentation(final String line) {
582 final Matcher matcher = LEADING_WHITESPACE.matcher(line);
583 if (matcher.find()) {
584 return matcher.end() - matcher.start();
585 }
586 return 0;
587 }
588
589
590
591
592 private void alerts(final List<String> lines, final String relativePath) {
593 for (int i = 0; i < lines.size(); i++) {
594 if (lines.get(i).startsWith(" @Alerts(")) {
595 final List<String> alerts = alertsToList(lines, i, true);
596 alertVerify(alerts, relativePath, i);
597 }
598 }
599 }
600
601
602
603
604 private static void classNameUsed(final List<String> lines, final List<String> classNames,
605 final String relativePath) {
606 String simpleName = relativePath.substring(0, relativePath.length() - 5);
607 simpleName = simpleName.substring(simpleName.lastIndexOf(File.separator) + 1);
608 for (final String line : lines) {
609 for (final Iterator<String> it = classNames.iterator(); it.hasNext();) {
610 final String className = it.next();
611 if (line.contains(className) && !className.equals(simpleName)) {
612 it.remove();
613 }
614 }
615 }
616 }
617
618
619
620
621 private void className(final List<String> lines, final String relativePath) {
622 if (relativePath.contains("main") && relativePath.contains("host")) {
623 String fileName = relativePath.substring(0, relativePath.length() - 5);
624 fileName = fileName.substring(fileName.lastIndexOf(File.separator) + 1);
625 String wrongName = null;
626 int i = 0;
627 for (final String line : lines) {
628 if (line.startsWith(" * ")) {
629 int p0 = line.indexOf("{@code ");
630 if (p0 != -1) {
631 p0 = p0 + "{@code ".length();
632 final int p1 = line.indexOf('}', p0 + 1);
633 final String name = line.substring(p0, p1);
634 if (!name.equals(fileName)) {
635 wrongName = name;
636 }
637 }
638 }
639 else if (line.startsWith("@JsxClass")) {
640 int p0 = line.indexOf("className = \"");
641 if (p0 != -1) {
642 p0 = p0 + "className = \"".length();
643 final int p1 = line.indexOf("\"", p0 + 1);
644 String name = line.substring(p0, p1);
645
646 if (Character.isLowerCase(name.charAt(0))) {
647 name = StringUtils.capitalize(name);
648 if (name.equals(fileName)) {
649 wrongName = null;
650 }
651 }
652 }
653 }
654 else if (line.startsWith("public class")) {
655 if (wrongName != null) {
656 addFailure(relativePath, i + 1, "Incorrect host class '" + wrongName + "'");
657 }
658 return;
659 }
660 ++i;
661 }
662 }
663 }
664
665
666
667
668
669
670
671
672 public static List<String> alertsToList(final List<String> lines, final int alertsIndex) {
673 return alertsToList(lines, alertsIndex, false);
674 }
675
676 private static List<String> alertsToList(final List<String> lines, final int alertsIndex,
677 final boolean preserveCommas) {
678 if (" @Alerts".equals(lines.get(alertsIndex))) {
679 lines.set(alertsIndex, " @Alerts()");
680 }
681 if (!lines.get(alertsIndex).startsWith(" @Alerts(")) {
682 throw new IllegalArgumentException("No @Alerts found in " + (alertsIndex + 1));
683 }
684 final StringBuilder alerts = new StringBuilder();
685 for (int i = alertsIndex;; i++) {
686 final String line = lines.get(i);
687 if (alerts.length() != 0) {
688 alerts.append('\n');
689 }
690 if (line.startsWith(" @Alerts(")) {
691 alerts.append(line.substring(" @Alerts(".length()));
692 }
693 else {
694 alerts.append(line);
695 }
696 if (line.endsWith(")")) {
697 alerts.deleteCharAt(alerts.length() - 1);
698 break;
699 }
700 }
701 final List<String> list = alertsToList(alerts.toString());
702 if (!preserveCommas) {
703 for (int i = 0; i < list.size(); i++) {
704 String value = list.get(i);
705 if (value.startsWith(",")) {
706 value = value.substring(1).trim();
707 }
708 list.set(i, value);
709 }
710 }
711 return list;
712 }
713
714
715
716
717 private void alertVerify(final List<String> alerts, final String relativePath, final int lineIndex) {
718 if (alerts.size() == 1) {
719 if (alerts.get(0).contains("DEFAULT")) {
720 addFailure(relativePath, lineIndex + 1, "No need for \"DEFAULT\"");
721 }
722 }
723 else {
724 final List<String> names = new ArrayList<>();
725 for (final String alert : alerts) {
726 String cleanedAlert = alert;
727 if (alert.charAt(0) == ',') {
728 if (alert.charAt(1) != '\n') {
729 addFailure(relativePath, lineIndex + 1, "Expectation must be in a separate line");
730 }
731 cleanedAlert = alert.substring(1).trim();
732 }
733
734 final int quoteIndex = cleanedAlert.indexOf('"');
735 final int equalsIndex = cleanedAlert.indexOf('=');
736 if (equalsIndex != -1 && equalsIndex < quoteIndex) {
737 final String name = cleanedAlert.substring(0, equalsIndex - 1);
738 alertVerifyOrder(name, names, relativePath, lineIndex);
739 names.add(name);
740 }
741 }
742 }
743 }
744
745
746
747
748 private static List<String> alertsToList(final String string) {
749 final List<String> list = new ArrayList<>();
750 if ("\"\"".equals(string)) {
751 list.add(string);
752 }
753 else {
754 final StringBuilder currentToken = new StringBuilder();
755
756 boolean insideString = true;
757 boolean startsWithBraces = false;
758 for (final String token : string.split("(?<!\\\\)\"")) {
759 insideString = !insideString;
760 if (currentToken.length() != 0) {
761 currentToken.append('"');
762 }
763 else {
764 startsWithBraces = token.toString().contains("{");
765 }
766
767 if (!insideString && token.startsWith(",") && !startsWithBraces) {
768 list.add(currentToken.toString());
769 currentToken.setLength(0);
770 startsWithBraces = token.toString().contains("{");
771 }
772
773 if (!insideString && token.contains("}")) {
774 final int curlyIndex = token.indexOf('}') + 1;
775 currentToken.append(token, 0, curlyIndex);
776 list.add(currentToken.toString());
777 currentToken.setLength(0);
778 currentToken.append(token, curlyIndex, token.length());
779 }
780 else {
781 if (!insideString && token.contains(",") && !startsWithBraces) {
782 final String[] expressions = token.split(",");
783 currentToken.append(expressions[0]);
784 if (currentToken.length() != 0) {
785 list.add(currentToken.toString());
786 }
787 for (int i = 1; i < expressions.length - 1; i++) {
788 list.add(',' + expressions[i]);
789 }
790 currentToken.setLength(0);
791 currentToken.append(',' + expressions[expressions.length - 1]);
792 }
793 else {
794 currentToken.append(token);
795 }
796 }
797 }
798 if (currentToken.length() != 0) {
799 if (!currentToken.toString().contains("\"")) {
800 currentToken.insert(0, '"');
801 }
802 int totalQuotes = 0;
803 for (int i = 0; i < currentToken.length(); i++) {
804 if (currentToken.charAt(i) == '"' && (i == 0 || currentToken.charAt(i - 1) != '\\')) {
805 totalQuotes++;
806 }
807 }
808 if (totalQuotes % 2 != 0) {
809 currentToken.append('"');
810 }
811
812 list.add(currentToken.toString());
813 }
814 }
815 return list;
816 }
817
818
819
820
821
822
823
824 private void alertVerifyOrder(final String browserName, final List<String> previousList,
825 final String relativePath, final int lineIndex) {
826 switch (browserName) {
827 case "DEFAULT":
828 if (!previousList.isEmpty()) {
829 addFailure(relativePath, lineIndex + 1, "DEFAULT must come first");
830 }
831 break;
832
833 default:
834 }
835 }
836
837
838
839
840 private void spaces(final List<String> lines, final String relativePath) {
841 for (int i = 0; i + 1 < lines.size(); i++) {
842 String line = lines.get(i).trim();
843 String next = lines.get(i + 1).trim();
844 if (line.startsWith("+ \"") && next.startsWith("+ \"")) {
845 line = line.substring(3);
846 final String lineTrimmed = line.trim();
847 next = next.substring(3);
848 if (lineTrimmed.startsWith("<") && next.trim().startsWith("<") || lineTrimmed.startsWith("try")
849 || lineTrimmed.startsWith("for") || lineTrimmed.startsWith("function")
850 || lineTrimmed.startsWith("if")) {
851 final int difference = getInitialSpaces(next) - getInitialSpaces(line);
852 if (difference > 2) {
853 addFailure(relativePath, i + 2, "Too many initial spaces");
854 }
855 else if (difference == 1) {
856 addFailure(relativePath, i + 2, "Add one more space");
857 }
858 }
859 }
860 }
861 }
862
863
864
865
866 private void indentation(final List<String> lines, final String relativePath) {
867 for (int i = 0; i + 1 < lines.size(); i++) {
868 final String line = lines.get(i);
869 if (line.startsWith(" CHROME = ")
870 || line.startsWith(" EDGE = ")
871 || line.startsWith(" FF = ")
872 || line.startsWith(" FF_ESR = ")) {
873 addFailure(relativePath, i + 2, "Incorrect indentation");
874 }
875 }
876 }
877
878 private static int getInitialSpaces(final String s) {
879 int spaces = 0;
880 while (spaces < s.length() && s.charAt(spaces) == ' ') {
881 spaces++;
882 }
883 return spaces;
884 }
885
886
887
888
889
890 @Test
891 public void tests() throws Exception {
892 title_ = "Tests";
893 testTests(new File("src/test/java"));
894 }
895
896 private void testTests(final File dir) throws Exception {
897 final File[] files = dir.listFiles();
898 if (files == null) {
899 return;
900 }
901
902 for (final File file : files) {
903 if (file.isDirectory()) {
904 if (!".git".equals(file.getName())) {
905 testTests(file);
906 }
907 }
908 else {
909 if (file.getName().endsWith(".java")) {
910 final int index = new File("src/test/java").getAbsolutePath().length();
911 String name = file.getAbsolutePath();
912 name = name.substring(index + 1, name.length() - 5);
913 name = name.replace(File.separatorChar, '.');
914 final Class<?> clazz;
915 try {
916 clazz = Class.forName(name);
917 }
918 catch (final Exception e) {
919 continue;
920 }
921 name = file.getName();
922 if (name.endsWith("Test.java") || name.endsWith("TestCase.java")) {
923 for (final Constructor<?> ctor : clazz.getConstructors()) {
924 if (ctor.getParameterTypes().length == 0) {
925 for (final Method method : clazz.getDeclaredMethods()) {
926 if (Modifier.isPublic(method.getModifiers())
927 && method.getAnnotation(BeforeEach.class) == null
928 && method.getAnnotation(BeforeAll.class) == null
929 && method.getAnnotation(AfterEach.class) == null
930 && method.getAnnotation(AfterAll.class) == null
931 && method.getAnnotation(Test.class) == null
932 && method.getAnnotation(RepeatedTest.class) == null
933 && method.getAnnotation(RetryingTest.class) == null
934 && method.getReturnType() == Void.TYPE
935 && method.getParameterTypes().length == 0) {
936 final List<String> lines = FileUtils.readLines(file, SOURCE_ENCODING);
937 int line = -1;
938 for (int i = 0; i < lines.size(); ++i) {
939 if (lines.get(i).contains("public void " + method.getName() + "()")) {
940 line = i + 1;
941 break;
942 }
943 }
944 addFailure(file.toString().replace('\\', '/'), line,
945 "Method '" + method.getName() + "' does not declare @Test annotation");
946 }
947 }
948 }
949 }
950 }
951 }
952 }
953 }
954 }
955
956 }