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