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