|
| 1 | +import javalang |
| 2 | +import os |
| 3 | +import re |
| 4 | + |
| 5 | + |
| 6 | +# Helper function to validate kebab-case |
| 7 | +def is_kebab_case(name): |
| 8 | + return re.match(r'^[a-z0-9]+(-[a-z0-9.]+)*$', name) is not None |
| 9 | + |
| 10 | +def find_java_files(directory): |
| 11 | + java_files = [] |
| 12 | + for root, _, files in os.walk(directory): |
| 13 | + for file in files: |
| 14 | + if file.endswith(".java"): |
| 15 | + java_files.append(os.path.join(root, file)) |
| 16 | + return java_files |
| 17 | + |
| 18 | +def parse_java_file(file_path): |
| 19 | + with open(file_path, 'r', encoding='utf-8') as file: |
| 20 | + return javalang.parse.parse(file.read()) |
| 21 | + |
| 22 | +def validate_inheritance(tree, file_name): |
| 23 | + issues = [] |
| 24 | + for path, node in tree: |
| 25 | + if isinstance(node, javalang.tree.ClassDeclaration): |
| 26 | + # Skip static inner classes |
| 27 | + if any(modifier for modifier in node.modifiers if modifier == "static") and '.' in node.name: |
| 28 | + continue |
| 29 | + |
| 30 | + if node.name.endswith("Decorator"): |
| 31 | + if not node.extends or "Decorator" not in node.extends.name: |
| 32 | + issues.append(f"{file_name}: Class '{node.name}' should extend a class with 'Decorator' in its name.") |
| 33 | + elif node.extends and "Decorator" in node.extends.name: |
| 34 | + if not node.name.endswith("Decorator"): |
| 35 | + issues.append(f"{file_name}: Class '{node.name}' extends a 'Decorator' class but does not end with 'Decorator'.") |
| 36 | + |
| 37 | + if node.name.endswith("Instrumentation"): |
| 38 | + # should extend something |
| 39 | + if not node.extends and not node.implements: |
| 40 | + issues.append(f"{file_name}: Class '{node.name}' should extend or implement a class with 'Instrument' in its name.") |
| 41 | + # if it extends something, it should extend an instrument |
| 42 | + elif not ((node.extends and "Instrument" in node.extends.name) or (node.implements and any("Instrument" in impl.name for impl in node.implements))): |
| 43 | + issues.append(f"{file_name}: Class '{node.name}' should extend a class with 'Instrument' in its name.") |
| 44 | + elif node.extends and "Instrument" in node.extends.name: |
| 45 | + if not node.name.endswith("Instrumentation"): |
| 46 | + issues.append(f"{file_name}: Class '{node.name}' extends a 'Instrument' class but does not end with 'Instrumentation'.") |
| 47 | + elif node.implements and any("Instrument" in impl for impl in node.implements): |
| 48 | + if not node.name.endswith("Instrumentation"): |
| 49 | + issues.append(f"{file_name}: Class '{node.name}' implements a 'Instrument' class but does not end with 'Instrumentation'.") |
| 50 | + |
| 51 | + if node.name.endswith("Advice"): |
| 52 | + # Check for methods with an @Advice annotation |
| 53 | + if not any( |
| 54 | + isinstance(member, javalang.tree.MethodDeclaration) and |
| 55 | + any(anno.name.startswith("Advice") for anno in member.annotations) |
| 56 | + for member in node.body |
| 57 | + ): |
| 58 | + issues.append(f"{file_name}: Class '{node.name}' does not have a method tagged with an @Advice annotation.") |
| 59 | + return issues |
| 60 | + |
| 61 | +def validate_project_structure(base_directory): |
| 62 | + issues = [] |
| 63 | + |
| 64 | + # Check kebab-case for subdirectories |
| 65 | + instrumentation_path = os.path.join(base_directory, "dd-java-agent", "instrumentation") |
| 66 | + if not os.path.exists(instrumentation_path): |
| 67 | + issues.append(f"Path '{instrumentation_path}' does not exist.") |
| 68 | + return issues |
| 69 | + |
| 70 | + for subdir in os.listdir(instrumentation_path): |
| 71 | + if os.path.isdir(os.path.join(instrumentation_path, subdir)): |
| 72 | + if not is_kebab_case(subdir): |
| 73 | + issues.append(f"Subdirectory '{subdir}' is not in kebab-case.") |
| 74 | + |
| 75 | + # Validate Java files in subdirectories |
| 76 | + for subdir in os.listdir(instrumentation_path): |
| 77 | + subdir_path = os.path.join(instrumentation_path, subdir) |
| 78 | + if os.path.isdir(subdir_path): |
| 79 | + java_files = find_java_files(os.path.join(subdir_path, "src", "main", "java")) |
| 80 | + for java_file in java_files: |
| 81 | + try: |
| 82 | + tree = parse_java_file(java_file) |
| 83 | + issues.extend(validate_inheritance(tree, java_file)) |
| 84 | + except (javalang.parser.JavaSyntaxError, UnicodeDecodeError): |
| 85 | + issues.append(f"Could not parse Java file '{java_file}'.") |
| 86 | + |
| 87 | + return issues |
| 88 | + |
| 89 | +if __name__ == "__main__": |
| 90 | + base_directory = "." # Change this to the root directory of your project |
| 91 | + problems = validate_project_structure(base_directory) |
| 92 | + |
| 93 | + if problems: |
| 94 | + print("\n".join(problems)) |
| 95 | + else: |
| 96 | + print("All checks passed!") |
0 commit comments