diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/hover/TokenHoverInfo.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/hover/TokenHoverInfo.java index 40da28d3f..2a1cece89 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/hover/TokenHoverInfo.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/hover/TokenHoverInfo.java @@ -5,10 +5,14 @@ import noppes.npcs.client.gui.util.script.interpreter.field.AssignmentInfo; import noppes.npcs.client.gui.util.script.interpreter.field.EnumConstantInfo; import noppes.npcs.client.gui.util.script.interpreter.field.FieldAccessInfo; import noppes.npcs.client.gui.util.script.interpreter.field.FieldInfo; +import noppes.npcs.client.gui.util.script.interpreter.jsdoc.JSDocDeprecatedTag; import noppes.npcs.client.gui.util.script.interpreter.jsdoc.JSDocInfo; import noppes.npcs.client.gui.util.script.interpreter.jsdoc.JSDocParamTag; import noppes.npcs.client.gui.util.script.interpreter.jsdoc.JSDocReturnTag; +import noppes.npcs.client.gui.util.script.interpreter.jsdoc.JSDocSeeTag; +import noppes.npcs.client.gui.util.script.interpreter.jsdoc.JSDocSinceTag; import noppes.npcs.client.gui.util.script.interpreter.jsdoc.JSDocTypeTag; +import noppes.npcs.client.gui.util.script.interpreter.js_parser.JSTypeInfo; import noppes.npcs.client.gui.util.script.interpreter.method.MethodCallInfo; import noppes.npcs.client.gui.util.script.interpreter.method.MethodInfo; import noppes.npcs.client.gui.util.script.interpreter.token.Token; @@ -1193,6 +1197,46 @@ public class TokenHoverInfo { jsDocLines.add(returnLine); } + + JSDocDeprecatedTag deprecatedTag = jsDoc.getDeprecatedTag(); + if (deprecatedTag != null) { + DocumentationLine deprecatedLine = new DocumentationLine(); + deprecatedLine.addSegment("@deprecated", TokenType.JSDOC_TAG.getHexColor()); + + if (deprecatedTag.hasReason()) { + deprecatedLine.addText(" - "); + deprecatedLine.addText(deprecatedTag.getReason()); + } + + jsDocLines.add(deprecatedLine); + } + + JSDocSinceTag sinceTag = jsDoc.getSinceTag(); + if (sinceTag != null) { + DocumentationLine sinceLine = new DocumentationLine(); + sinceLine.addSegment("Since:", TokenType.JSDOC_TAG.getHexColor()); + sinceLine.addText(" " + sinceTag.getVersion()); + + jsDocLines.add(sinceLine); + } + + List seeTags = jsDoc.getSeeTags(); + if (seeTags != null && !seeTags.isEmpty()) { + for (JSDocSeeTag seeTag : seeTags) { + DocumentationLine seeLine = new DocumentationLine(); + seeLine.addSegment("See:", TokenType.JSDOC_TAG.getHexColor()); + + String reference = seeTag.getReference(); + if (seeTag.hasLinkText()) { + seeLine.addText(" "); + seeLine.addSegment(seeTag.getLinkText(), TokenType.INTERFACE_DECL.getHexColor()); + } else if (reference != null) { + seeLine.addText(" " + reference); + } + + jsDocLines.add(seeLine); + } + } } private void extractJavadoc(Method method) { diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/js_parser/DTSJSDocParser.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/js_parser/DTSJSDocParser.java new file mode 100644 index 000000000..ecf41f240 --- /dev/null +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/js_parser/DTSJSDocParser.java @@ -0,0 +1,203 @@ +package noppes.npcs.client.gui.util.script.interpreter.js_parser; + +import noppes.npcs.client.gui.util.script.interpreter.jsdoc.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class DTSJSDocParser { + + private static final Pattern JSDOC_BLOCK_PATTERN = Pattern.compile( + "/\\*\\*\\s*(.*?)\\*/", Pattern.DOTALL); + + private static final Pattern TAG_PATTERN = Pattern.compile( + "@(\\w+)(?:\\s+(.*))?"); + + private static final Pattern PARAM_PATTERN = Pattern.compile( + "@param\\s+(?:\\{([^}]+)\\}\\s+)?(\\w+)(?:\\s+(.*))?"); + + private static final Pattern RETURN_PATTERN = Pattern.compile( + "@returns?\\s+(?:\\{([^}]+)\\}\\s*)?(.*)"); + + private static final Pattern TYPE_PATTERN = Pattern.compile( + "@type\\s+\\{([^}]+)\\}(?:\\s+(.*))?"); + + public static JSDocInfo parseJSDocBlock(String jsDocContent) { + if (jsDocContent == null || jsDocContent.isEmpty()) { + return null; + } + + JSDocInfo info = new JSDocInfo(jsDocContent, -1, -1); + + String[] lines = jsDocContent.split("\\r?\\n"); + StringBuilder descriptionBuilder = new StringBuilder(); + boolean foundFirstTag = false; + JSDocTag lastTag = null; + + for (String line : lines) { + line = cleanLine(line); + if (line.isEmpty()) continue; + + if (line.startsWith("@")) { + foundFirstTag = true; + lastTag = parseTag(line, info); + } else if (!foundFirstTag) { + if (descriptionBuilder.length() > 0) { + descriptionBuilder.append(" "); + } + descriptionBuilder.append(line); + } else if (lastTag != null) { + String existing = lastTag.getDescription(); + if (existing == null || existing.isEmpty()) { + lastTag.setDescription(line); + } else { + lastTag.setDescription(existing + " " + line); + } + } + } + + if (descriptionBuilder.length() > 0) { + info.setDescription(descriptionBuilder.toString().trim()); + } + + return info; + } + + private static String cleanLine(String line) { + line = line.trim(); + if (line.startsWith("/**")) { + line = line.substring(3).trim(); + } + if (line.endsWith("*/")) { + line = line.substring(0, line.length() - 2).trim(); + } + if (line.startsWith("*")) { + line = line.substring(1).trim(); + } + return line; + } + + private static JSDocTag parseTag(String line, JSDocInfo info) { + Matcher paramMatcher = PARAM_PATTERN.matcher(line); + if (paramMatcher.find()) { + String type = paramMatcher.group(1); + String name = paramMatcher.group(2); + String desc = paramMatcher.group(3); + JSDocParamTag tag = JSDocParamTag.create(-1, -1, -1, + type, null, -1, -1, name, -1, -1, desc != null ? desc.trim() : null); + info.addParamTag(tag); + return tag; + } + + Matcher returnMatcher = RETURN_PATTERN.matcher(line); + if (returnMatcher.find()) { + String type = returnMatcher.group(1); + String desc = returnMatcher.group(2); + if (desc != null) { + desc = desc.trim(); + if (desc.isEmpty()) { + desc = null; + } + } + JSDocReturnTag tag = JSDocReturnTag.create("return", -1, -1, -1, + type, null, -1, -1, desc); + info.setReturnTag(tag); + return tag; + } + + Matcher typeMatcher = TYPE_PATTERN.matcher(line); + if (typeMatcher.find()) { + String type = typeMatcher.group(1); + String desc = typeMatcher.group(2); + JSDocTypeTag tag = JSDocTypeTag.create(-1, -1, -1, + type, null, -1, -1, desc != null ? desc.trim() : null); + info.setTypeTag(tag); + return tag; + } + + Matcher tagMatcher = TAG_PATTERN.matcher(line); + if (tagMatcher.find()) { + String tagName = tagMatcher.group(1); + String rest = tagMatcher.group(2); + + switch (tagName) { + case "since": + JSDocSinceTag sinceTag = JSDocSinceTag.createSimple(rest != null ? rest.trim() : ""); + info.setSinceTag(sinceTag); + return sinceTag; + case "deprecated": + JSDocDeprecatedTag deprecatedTag = JSDocDeprecatedTag.createSimple(rest != null ? rest.trim() : ""); + info.setDeprecatedTag(deprecatedTag); + return deprecatedTag; + case "see": + JSDocSeeTag seeTag = JSDocSeeTag.createSimple(rest != null ? rest.trim() : ""); + info.addSeeTag(seeTag); + return seeTag; + default: + JSDocTag genericTag = new JSDocTag(tagName, -1, -1, -1); + genericTag.setDescription(rest != null ? rest.trim() : ""); + info.addTag(genericTag); + return genericTag; + } + } + return null; + } + + public static String extractJSDocBefore(String content, int elementStart) { + if (elementStart <= 0) return null; + + int searchStart = Math.max(0, elementStart - 2000); + String searchArea = content.substring(searchStart, elementStart); + + int lastJSDocEnd = searchArea.lastIndexOf("*/"); + if (lastJSDocEnd < 0) return null; + + int jsDocStart = searchArea.lastIndexOf("/**", lastJSDocEnd); + if (jsDocStart < 0) return null; + + String between = searchArea.substring(lastJSDocEnd + 2).trim(); + if (between.isEmpty() || isOnlyWhitespaceOrModifiers(between)) { + return searchArea.substring(jsDocStart, lastJSDocEnd + 2); + } + + return null; + } + + private static boolean isOnlyWhitespaceOrModifiers(String text) { + String cleaned = text.replaceAll("\\s+", " ").trim(); + return cleaned.isEmpty() + || cleaned.matches("^(export\\s*)?(readonly\\s*)?(interface|class|type|function)?\\s*$"); + } + + public static List extractAllJSDocBlocks(String content) { + List blocks = new ArrayList<>(); + Matcher matcher = JSDOC_BLOCK_PATTERN.matcher(content); + + while (matcher.find()) { + int start = matcher.start(); + int end = matcher.end(); + String docContent = matcher.group(0); + blocks.add(new JSDocBlock(start, end, docContent)); + } + + return blocks; + } + + public static class JSDocBlock { + public final int start; + public final int end; + public final String content; + + public JSDocBlock(int start, int end, String content) { + this.start = start; + this.end = end; + this.content = content; + } + + public JSDocInfo parse() { + return parseJSDocBlock(content); + } + } +} diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/jsdoc/JSDocDeprecatedTag.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/jsdoc/JSDocDeprecatedTag.java new file mode 100644 index 000000000..f5f32d503 --- /dev/null +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/jsdoc/JSDocDeprecatedTag.java @@ -0,0 +1,34 @@ +package noppes.npcs.client.gui.util.script.interpreter.jsdoc; + +public class JSDocDeprecatedTag extends JSDocTag { + + private final String reason; + + public JSDocDeprecatedTag(String tagName, int atSignOffset, int tagNameStart, int tagNameEnd, String reason) { + super(tagName, atSignOffset, tagNameStart, tagNameEnd); + this.reason = reason; + this.setDescription(reason); + } + + public static JSDocDeprecatedTag create(String tagName, int atSignOffset, int tagNameStart, int tagNameEnd, + String reason) { + return new JSDocDeprecatedTag(tagName, atSignOffset, tagNameStart, tagNameEnd, reason); + } + + public static JSDocDeprecatedTag createSimple(String reason) { + return new JSDocDeprecatedTag("deprecated", -1, -1, -1, reason); + } + + public String getReason() { + return reason; + } + + public boolean hasReason() { + return reason != null && !reason.isEmpty(); + } + + @Override + public String toString() { + return "JSDocDeprecatedTag{@deprecated" + (hasReason() ? " " + reason : "") + "}"; + } +} diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/jsdoc/JSDocInfo.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/jsdoc/JSDocInfo.java index 1cd0050b4..9591ca958 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/jsdoc/JSDocInfo.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/jsdoc/JSDocInfo.java @@ -19,6 +19,9 @@ public class JSDocInfo { private JSDocTypeTag typeTag; private final List paramTags = new ArrayList<>(); private JSDocReturnTag returnTag; + private JSDocDeprecatedTag deprecatedTag; + private JSDocSinceTag sinceTag; + private final List seeTags = new ArrayList<>(); private String description; private final List allTags = new ArrayList<>(); @@ -43,6 +46,21 @@ public class JSDocInfo { allTags.add(returnTag); } + public void setDeprecatedTag(JSDocDeprecatedTag deprecatedTag) { + this.deprecatedTag = deprecatedTag; + allTags.add(deprecatedTag); + } + + public void setSinceTag(JSDocSinceTag sinceTag) { + this.sinceTag = sinceTag; + allTags.add(sinceTag); + } + + public void addSeeTag(JSDocSeeTag seeTag) { + this.seeTags.add(seeTag); + allTags.add(seeTag); + } + public void setDescription(String description) { this.description = description; } @@ -83,6 +101,15 @@ public class JSDocInfo { return returnTag != null ? returnTag.getTypeInfo() : null; } + public JSDocDeprecatedTag getDeprecatedTag() { return deprecatedTag; } + public boolean isDeprecated() { return deprecatedTag != null; } + + public JSDocSinceTag getSinceTag() { return sinceTag; } + public boolean hasSince() { return sinceTag != null; } + + public List getSeeTags() { return Collections.unmodifiableList(seeTags); } + public boolean hasSeeTags() { return !seeTags.isEmpty(); } + public String getDescription() { return description; } public List getAllTags() { return Collections.unmodifiableList(allTags); } @@ -92,6 +119,9 @@ public class JSDocInfo { if (typeTag != null) sb.append("type=").append(typeTag.getTypeName()); if (!paramTags.isEmpty()) sb.append(", params=").append(paramTags.size()); if (returnTag != null) sb.append(", return=").append(returnTag.getTypeName()); + if (deprecatedTag != null) sb.append(", deprecated"); + if (sinceTag != null) sb.append(", since=").append(sinceTag.getVersion()); + if (!seeTags.isEmpty()) sb.append(", see=").append(seeTags.size()); sb.append("}"); return sb.toString(); } diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/jsdoc/JSDocSeeTag.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/jsdoc/JSDocSeeTag.java new file mode 100644 index 000000000..028023c6e --- /dev/null +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/jsdoc/JSDocSeeTag.java @@ -0,0 +1,75 @@ +package noppes.npcs.client.gui.util.script.interpreter.jsdoc; + +public class JSDocSeeTag extends JSDocTag { + + private final String reference; + private String url; + private String linkText; + + public JSDocSeeTag(String tagName, int atSignOffset, int tagNameStart, int tagNameEnd, String reference) { + super(tagName, atSignOffset, tagNameStart, tagNameEnd); + this.reference = reference; + this.setDescription(reference); + parseReference(reference); + } + + public static JSDocSeeTag create(String tagName, int atSignOffset, int tagNameStart, int tagNameEnd, + String reference) { + return new JSDocSeeTag(tagName, atSignOffset, tagNameStart, tagNameEnd, reference); + } + + public static JSDocSeeTag createSimple(String reference) { + return new JSDocSeeTag("see", -1, -1, -1, reference); + } + + private void parseReference(String ref) { + if (ref == null) return; + + if (ref.contains(""); + int textEnd = ref.indexOf(""); + if (textStart != -1 && textEnd != -1 && textStart < textEnd) { + this.linkText = ref.substring(textStart + 1, textEnd); + } + } else if (ref.contains("{@link")) { + int linkStart = ref.indexOf("{@link"); + int linkEnd = ref.indexOf("}", linkStart); + if (linkStart != -1 && linkEnd != -1) { + this.linkText = ref.substring(linkStart + 6, linkEnd).trim(); + } + } + } + + public String getReference() { + return reference; + } + + public String getUrl() { + return url; + } + + public boolean hasUrl() { + return url != null && !url.isEmpty(); + } + + public String getLinkText() { + return linkText; + } + + public boolean hasLinkText() { + return linkText != null && !linkText.isEmpty(); + } + + @Override + public String toString() { + return "JSDocSeeTag{@see " + reference + "}"; + } +} diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/jsdoc/JSDocSinceTag.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/jsdoc/JSDocSinceTag.java new file mode 100644 index 000000000..6c9005b0b --- /dev/null +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/jsdoc/JSDocSinceTag.java @@ -0,0 +1,30 @@ +package noppes.npcs.client.gui.util.script.interpreter.jsdoc; + +public class JSDocSinceTag extends JSDocTag { + + private final String version; + + public JSDocSinceTag(String tagName, int atSignOffset, int tagNameStart, int tagNameEnd, String version) { + super(tagName, atSignOffset, tagNameStart, tagNameEnd); + this.version = version; + this.setDescription(version); + } + + public static JSDocSinceTag create(String tagName, int atSignOffset, int tagNameStart, int tagNameEnd, + String version) { + return new JSDocSinceTag(tagName, atSignOffset, tagNameStart, tagNameEnd, version); + } + + public static JSDocSinceTag createSimple(String version) { + return new JSDocSinceTag("since", -1, -1, -1, version); + } + + public String getVersion() { + return version; + } + + @Override + public String toString() { + return "JSDocSinceTag{@since " + version + "}"; + } +}