diff --git a/nobuild/NoBuild.java b/nobuild/NoBuild.java index 214c0ec..6f43103 100644 --- a/nobuild/NoBuild.java +++ b/nobuild/NoBuild.java @@ -324,6 +324,7 @@ public class NoBuild { fileManager.getJavaFileObjects(sourcePaths); List compilerOptions = new ArrayList<>(); + compilerOptions.add("-Xlint:all"); compilerOptions.add("-d"); compilerOptions.add(classOutputPath); diff --git a/src/main/java/xyz/naofal/jtags/Jtags.java b/src/main/java/xyz/naofal/jtags/Jtags.java index 5ad4612..1543b45 100644 --- a/src/main/java/xyz/naofal/jtags/Jtags.java +++ b/src/main/java/xyz/naofal/jtags/Jtags.java @@ -15,7 +15,7 @@ public class Jtags { boolean absolutePaths = false; boolean excludeNonPublic = false; boolean excludeAnonymous = false; - boolean excludeStaticField = false; + List> fields = new ArrayList<>(); } public static void main(String[] args) { @@ -40,11 +40,6 @@ public class Jtags { options.absolutePaths = true; break; - case "-no-static": - logger.config("Excluding 'file:' field in tags"); - options.excludeStaticField = true; - break; - case "-no-non-public": logger.config("Excluding non-public elements"); options.excludeNonPublic = true; @@ -55,10 +50,47 @@ public class Jtags { options.excludeAnonymous = true; break; + case "-fields": + String fields = + switch (arguments.poll()) { + case null -> { + logger.severe("Expected argument after " + argument); + printUsage(); + System.exit(1); + yield null; + } + case String s -> s; + }; + + options.fields = + fields + .chars() + .>mapToObj( + it -> + (Class) + switch (it) { + case 's' -> TagField.StaticTag.class; + case 'p' -> TagField.Package.class; + case 't' -> TagField.EnclosingType.class; + default -> { + logger.severe("Unknown field: " + it); + printUsage(); + System.exit(1); + yield null; + } + }) + .toList(); + + logger.config( + () -> + "Including fields: " + + String.join( + ", ", options.fields.stream().map(it -> it.getSimpleName()).toList())); + break; + case "-lib": logger.config("Third-party library mode"); options.absolutePaths = true; - options.excludeStaticField = true; options.excludeNonPublic = true; options.excludeAnonymous = true; break; @@ -83,6 +115,12 @@ public class Jtags { } } + if (options.sources.isEmpty()) { + logger.severe("No source files provided"); + printUsage(); + System.exit(1); + } + System.exit(run(options) ? 0 : 1); } @@ -96,16 +134,20 @@ public class Jtags { static void printUsage() { System.err.println( """ - Usage: jtags [options] - Options: - -o, -output Write tags to specified - -lib Treat sources as third-party libraries - (alias for -absolute -no-non-public -no-anonymous) - -absolute Use absolute paths for tag locations - -no-static Exclude the "file:" field from tags - -no-anonymous Exclude anonymous classes - -no-anonymous Exclude anonymous classes - -h, -help Show this message - """); +Usage: jtags [options] +Options: + -o, -output Write tags to specified + -lib Treat sources as third-party libraries + (alias for -absolute -no-non-public -no-anonymous) + -no-anonymous Exclude anonymous classes + -no-non-public Exclude non-public elements + -absolute Use absolute paths for tag locations + -fields Fields to include in tag entries. Default: spt + Avaiblable fields are: + s static tag + p package + t enclosing type + -h, -help Show this message +"""); } } diff --git a/src/main/java/xyz/naofal/jtags/Tag.java b/src/main/java/xyz/naofal/jtags/Tag.java index 6352e25..0ca0bc9 100644 --- a/src/main/java/xyz/naofal/jtags/Tag.java +++ b/src/main/java/xyz/naofal/jtags/Tag.java @@ -2,12 +2,13 @@ package xyz.naofal.jtags; import java.nio.file.Path; import java.util.Comparator; +import java.util.List; -public record Tag(TagKind kind, String name, Path location, String line, boolean isStatic) +public record Tag(TagKind kind, String name, Path location, String line, List fields) implements Comparable { public Tag(TagKind kind, String name, Path location, String line) { - this(kind, name, location, line, false); + this(kind, name, location, line, List.of()); } @Override diff --git a/src/main/java/xyz/naofal/jtags/TagField.java b/src/main/java/xyz/naofal/jtags/TagField.java new file mode 100644 index 0000000..1fde879 --- /dev/null +++ b/src/main/java/xyz/naofal/jtags/TagField.java @@ -0,0 +1,9 @@ +package xyz.naofal.jtags; + +public sealed interface TagField { + public record StaticTag() implements TagField {} + + public record Package(String p) implements TagField {} + + public record EnclosingType(String type, TagKind kind) implements TagField {} +} diff --git a/src/main/java/xyz/naofal/jtags/TagsWriter.java b/src/main/java/xyz/naofal/jtags/TagsWriter.java index 5e7763a..4273ddc 100644 --- a/src/main/java/xyz/naofal/jtags/TagsWriter.java +++ b/src/main/java/xyz/naofal/jtags/TagsWriter.java @@ -1,6 +1,9 @@ package xyz.naofal.jtags; import static xyz.naofal.jtags.JtagsLogger.logger; +import static xyz.naofal.jtags.TagField.EnclosingType; +import static xyz.naofal.jtags.TagField.Package; +import static xyz.naofal.jtags.TagField.StaticTag; import java.io.FileOutputStream; import java.io.IOException; @@ -60,8 +63,16 @@ public record TagsWriter(Options options) { case ENUM_CONSTANT -> 'e'; case METHOD -> 'm'; }); - if (!options().excludeStaticField && tag.isStatic()) { - writer.write("\tfile:"); + + for (TagField field : tag.fields()) { + writer.write('\t'); + writer.write( + switch (field) { + case StaticTag() -> "file:"; + case Package(var p) -> "package:" + p; + case EnclosingType(var t, var k) -> + k.name().toLowerCase() + ":" + (t.isEmpty() ? "(Anonymous)" : t); + }); } writer.write('\n'); diff --git a/src/main/java/xyz/naofal/jtags/TreeVisitor.java b/src/main/java/xyz/naofal/jtags/TreeVisitor.java index fbb43a2..7d4dd5d 100644 --- a/src/main/java/xyz/naofal/jtags/TreeVisitor.java +++ b/src/main/java/xyz/naofal/jtags/TreeVisitor.java @@ -9,7 +9,9 @@ import com.sun.source.tree.PackageTree; import com.sun.source.tree.Tree; import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePathScanner; +import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.PriorityQueue; import javax.lang.model.element.Modifier; import xyz.naofal.jtags.Jtags.Options; @@ -46,10 +48,11 @@ public class TreeVisitor extends TreePathScanner { @Override public Void visitClass(ClassTree node, TreeVisitorContext p) { - if (options.excludeAnonymous && node.getSimpleName().isEmpty()) { + if (options.excludeNonPublic && !node.getModifiers().getFlags().contains(Modifier.PUBLIC)) { return null; } - if (options.excludeNonPublic && !node.getModifiers().getFlags().contains(Modifier.PUBLIC)) { + + if (options.excludeAnonymous && node.getSimpleName().isEmpty()) { return null; } @@ -57,23 +60,40 @@ public class TreeVisitor extends TreePathScanner { return scan(node.getMembers(), p); } + TagKind typeKind = getTypeKind(node); + + List fields = new ArrayList<>(); + + if (options.fields.contains(TagField.StaticTag.class) + && node.getModifiers().getFlags().contains(Modifier.STATIC)) { + fields.add(new TagField.StaticTag()); + } + + if (options.fields.contains(TagField.Package.class)) { + for (var path : getCurrentPath().getParentPath()) { + if (path instanceof CompilationUnitTree compilationUnitTree) { + fields.add( + new TagField.Package( + Optional.ofNullable(compilationUnitTree.getPackageName()) + .map(String::valueOf) + .orElse(""))); + break; + } + } + } + + if (options.fields.contains(TagField.EnclosingType.class)) { + for (var path : getCurrentPath().getParentPath()) { + if (path instanceof ClassTree classTree) { + fields.add(new TagField.EnclosingType(classTree.getSimpleName().toString(), typeKind)); + break; + } + } + } + Tag tag = new Tag( - switch (node.getKind()) { - case CLASS -> TagKind.CLASS; - case RECORD -> TagKind.RECORD; - case INTERFACE -> TagKind.INTERFACE; - case ENUM -> TagKind.ENUM; - case ANNOTATION_TYPE -> TagKind.ANNOTATION; - default -> { - logger.warning("Unknown class kind " + node.getKind()); - yield TagKind.CLASS; - } - }, - node.getSimpleName().toString(), - p.getLocation(), - p.getLine(node), - node.getModifiers().getFlags().contains(Modifier.STATIC)); + typeKind, node.getSimpleName().toString(), p.getLocation(), p.getLine(node), fields); logger.finer(() -> "Type: " + tag); @@ -88,6 +108,24 @@ public class TreeVisitor extends TreePathScanner { return null; } + List fields = new ArrayList<>(); + + if (options.fields.contains(TagField.StaticTag.class) + && node.getModifiers().getFlags().contains(Modifier.STATIC)) { + fields.add(new TagField.StaticTag()); + } + + if (options.fields.contains(TagField.EnclosingType.class)) { + for (var path : getCurrentPath().getParentPath()) { + if (path instanceof ClassTree classTree) { + fields.add( + new TagField.EnclosingType( + classTree.getSimpleName().toString(), getTypeKind(classTree))); + break; + } + } + } + Tag tag = new Tag( switch (node.getKind()) { @@ -104,7 +142,7 @@ public class TreeVisitor extends TreePathScanner { .toString(), p.getLocation(), p.getLine(node), - node.getModifiers().getFlags().contains(Modifier.STATIC)); + fields); logger.finer(() -> "Method: " + tag); @@ -125,9 +163,23 @@ public class TreeVisitor extends TreePathScanner { return scan(node.getInitializer(), p); } + TagKind enclosingTypeKind = getTypeKind(enclosingType); + + List fields = new ArrayList<>(); + + if (options.fields.contains(TagField.StaticTag.class) + && node.getModifiers().getFlags().contains(Modifier.STATIC)) { + fields.add(new TagField.StaticTag()); + } + + if (options.fields.contains(TagField.EnclosingType.class)) { + fields.add( + new TagField.EnclosingType(enclosingType.getSimpleName().toString(), enclosingTypeKind)); + } + Tag tag = new Tag( - enclosingType.getKind() == Tree.Kind.ENUM + enclosingTypeKind == TagKind.ENUM && node.getModifiers() .getFlags() .containsAll(List.of(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)) @@ -136,7 +188,7 @@ public class TreeVisitor extends TreePathScanner { node.getName().toString(), p.getLocation(), p.getLine(node), - node.getModifiers().getFlags().contains(Modifier.STATIC)); + fields); logger.finer(() -> "Variable: " + tag); @@ -144,4 +196,18 @@ public class TreeVisitor extends TreePathScanner { return scan(node.getInitializer(), p); } + + private TagKind getTypeKind(ClassTree classTree) { + return switch (classTree.getKind()) { + case CLASS -> TagKind.CLASS; + case RECORD -> TagKind.RECORD; + case INTERFACE -> TagKind.INTERFACE; + case ENUM -> TagKind.ENUM; + case ANNOTATION_TYPE -> TagKind.ANNOTATION; + default -> { + logger.warning("Unknown class kind " + classTree.getKind()); + yield TagKind.CLASS; + } + }; + } }