Proper parsing for generic JSDoc tags like "@word this is a tag"

Removed deprecated/since tags, parsed them as generics instead
This commit is contained in:
bigguy345 committed 2026-01-24 03:29:48 +02:00
1 parent 021a32b962
commit 1b9e674b36
6 files changed
+227 -191

No files matched your search

@@ -5,12 +5,11 @@ 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.JSDocTag;
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;
@@ -1224,28 +1223,6 @@ 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<JSDocSeeTag> seeTags = jsDoc.getSeeTags();
if (seeTags != null && !seeTags.isEmpty()) {
for (JSDocSeeTag seeTag : seeTags) {
@@ -1263,6 +1240,44 @@ public class TokenHoverInfo {
jsDocLines.add(seeLine);
}
}
List<JSDocTag> allTags = jsDoc.getAllTags();
if (allTags != null && !allTags.isEmpty()) {
for (JSDocTag tag : allTags) {
String tagName = tag.getTagName();
if (tagName == null) {
continue;
}
String normalized = tagName.toLowerCase();
if ("type".equals(normalized)
|| "param".equals(normalized)
|| "return".equals(normalized)
|| "returns".equals(normalized)
|| "see".equals(normalized)) {
continue;
}
DocumentationLine tagLine = new DocumentationLine();
//capitalise first letter in tag name
String capTagName = tagName.substring(0, 1).toUpperCase() + tagName.substring(1);
tagLine.addSegment(capTagName + ":", TokenType.JSDOC_TAG.getHexColor());
if (tag.hasType()) {
tagLine.addSegment(" {", TokenType.JSDOC_TYPE.getHexColor());
tagLine.addSegment(tag.getTypeName(), TokenType.getColor(tag.getTypeInfo()));
tagLine.addSegment("}", TokenType.JSDOC_TYPE.getHexColor());
}
String tagDesc = tag.getDescription();
if (tagDesc != null && !tagDesc.isEmpty()) {
tagLine.addText(" - ");
tagLine.addText(tagDesc.trim());
}
jsDocLines.add(tagLine);
}
}
}
private void extractJavadoc(Method method) {
@@ -13,7 +13,7 @@ public class DTSJSDocParser {
"/\\*\\*\\s*(.*?)\\*/", Pattern.DOTALL);
private static final Pattern TAG_PATTERN = Pattern.compile(
"@(\\w+)(?:\\s+(.*))?");
"@(\\w+)(?:\\s+\\{([^}]+)\\})?(?:\\s+(.*))?");
private static final Pattern PARAM_PATTERN = Pattern.compile(
"@param(?:\\s+\\{([^}]+)\\})?(?:\\s+(\\w+))?(?:\\s+(.*))?");
@@ -136,23 +136,20 @@ public class DTSJSDocParser {
Matcher tagMatcher = TAG_PATTERN.matcher(line);
if (tagMatcher.find()) {
String tagName = tagMatcher.group(1);
String rest = tagMatcher.group(2);
String typeName = tagMatcher.group(2);
String rest = tagMatcher.group(3);
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);
if (typeName != null) {
typeName = typeName.trim();
genericTag.setType(typeName, null, -1, -1);
}
genericTag.setDescription(rest != null ? rest.trim() : "");
info.addTag(genericTag);
return genericTag;
@@ -1,34 +0,0 @@
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 : "") + "}";
}
}
@@ -19,8 +19,6 @@ public class JSDocInfo {
private JSDocTypeTag typeTag;
private final List<JSDocParamTag> paramTags = new ArrayList<>();
private JSDocReturnTag returnTag;
private JSDocDeprecatedTag deprecatedTag;
private JSDocSinceTag sinceTag;
private final List<JSDocSeeTag> seeTags = new ArrayList<>();
private String description;
private final List<JSDocTag> allTags = new ArrayList<>();
@@ -46,15 +44,6 @@ 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);
@@ -102,12 +91,6 @@ 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<JSDocSeeTag> getSeeTags() { return Collections.unmodifiableList(seeTags); }
public boolean hasSeeTags() { return !seeTags.isEmpty(); }
@@ -120,8 +103,6 @@ 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();
@@ -18,16 +18,16 @@ public class JSDocParser {
"/\\*\\*([\\s\\S]*?)\\*/", Pattern.MULTILINE);
private static final Pattern TYPE_TAG_PATTERN = Pattern.compile(
"@(type)\\s*\\{([^}]+)\\}(?:\\s*-?\\s*([^@\\n]*))?", Pattern.CASE_INSENSITIVE);
"^@(type)\\s*(?:\\{([^}]+)\\})?(?:\\s*-?\\s*(.*))?$", Pattern.CASE_INSENSITIVE);
private static final Pattern PARAM_TAG_PATTERN = Pattern.compile(
"@(param)\\s*(?:\\{([^}]+)\\})?\\s*(\\w+)(?:\\s*-?\\s*([^@\\n]*))?", Pattern.CASE_INSENSITIVE);
"^@param\\s*(?:\\{([^}]+)\\})?\\s*(\\w+)?(?:\\s*-?\\s*(.*))?$", Pattern.CASE_INSENSITIVE);
private static final Pattern RETURN_TAG_PATTERN = Pattern.compile(
"@(returns?)\\s*(?:\\{([^}]+)\\})?(?:\\s*-?\\s*([^@\\n]*))?", Pattern.CASE_INSENSITIVE);
"^@(returns?)\\s*(?:\\{([^}]+)\\})?(?:\\s*-?\\s*(.*))?$", Pattern.CASE_INSENSITIVE);
private static final Pattern ANY_TAG_PATTERN = Pattern.compile(
"@(\\w+)", Pattern.CASE_INSENSITIVE);
private static final Pattern GENERIC_TAG_PATTERN = Pattern.compile(
"^@(\\w+)(?:\\s*\\{([^}]+)\\})?(?:\\s*-?\\s*(.*))?$", Pattern.CASE_INSENSITIVE);
private final ScriptDocument document;
@@ -50,10 +50,53 @@ public class JSDocParser {
int contentOffset = commentStart + 3;
parseTypeTag(content, contentOffset, info);
parseParamTags(content, contentOffset, info);
parseReturnTag(content, contentOffset, info);
extractDescription(content, info);
StringBuilder descriptionBuilder = new StringBuilder();
boolean foundFirstTag = false;
JSDocTag lastTag = null;
int position = 0;
while (position <= content.length()) {
int lineEnd = content.indexOf('\n', position);
if (lineEnd == -1) {
lineEnd = content.length();
}
String rawLine = content.substring(position, lineEnd);
if (rawLine.endsWith("\r")) {
rawLine = rawLine.substring(0, rawLine.length() - 1);
}
CleanLine cleanLine = cleanLine(rawLine, contentOffset + position);
String line = cleanLine.text;
if (!line.isEmpty()) {
if (line.startsWith("@")) {
foundFirstTag = true;
lastTag = parseTagLine(line, cleanLine.offset, 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 (lineEnd == content.length()) {
break;
}
position = lineEnd + 1;
}
if (descriptionBuilder.length() > 0) {
info.setDescription(descriptionBuilder.toString().trim());
}
return info;
}
@@ -97,39 +140,16 @@ public class JSDocParser {
return parse(comment, startCommentPos);
}
private void parseTypeTag(String content, int contentOffset, JSDocInfo info) {
Matcher m = TYPE_TAG_PATTERN.matcher(content);
if (m.find()) {
int atSignOffset = contentOffset + m.start();
int tagNameStart = contentOffset + m.start(1);
int tagNameEnd = contentOffset + m.end(1);
private JSDocTag parseTagLine(String line, int lineOffset, JSDocInfo info) {
Matcher paramMatcher = PARAM_TAG_PATTERN.matcher(line);
if (paramMatcher.matches()) {
String typeName = paramMatcher.group(1);
String paramName = paramMatcher.group(2);
String description = normalizeDescription(paramMatcher.group(3));
String typeName = m.group(2).trim();
String description = m.group(3);
int braceStart = content.indexOf('{', m.start());
int braceEnd = content.indexOf('}', braceStart);
int typeStart = contentOffset + braceStart + 1;
int typeEnd = contentOffset + braceEnd;
TypeInfo typeInfo = resolveType(typeName);
JSDocTypeTag tag = JSDocTypeTag.create(atSignOffset, tagNameStart, tagNameEnd,
typeName, typeInfo, typeStart, typeEnd, description != null ? description.trim() : null);
info.setTypeTag(tag);
}
}
private void parseParamTags(String content, int contentOffset, JSDocInfo info) {
Matcher m = PARAM_TAG_PATTERN.matcher(content);
while (m.find()) {
int atSignOffset = contentOffset + m.start();
int tagNameStart = contentOffset + m.start(1);
int tagNameEnd = contentOffset + m.end(1);
String typeName = m.group(2);
String paramName = m.group(3);
String description = m.group(4);
int atSignOffset = lineOffset + paramMatcher.start();
int tagNameStart = lineOffset + 1;
int tagNameEnd = tagNameStart + "param".length();
int typeStart = -1;
int typeEnd = -1;
@@ -137,34 +157,35 @@ public class JSDocParser {
if (typeName != null) {
typeName = typeName.trim();
int braceStart = content.indexOf('{', m.start());
int braceEnd = content.indexOf('}', braceStart);
typeStart = contentOffset + braceStart + 1;
typeEnd = contentOffset + braceEnd;
typeStart = lineOffset + paramMatcher.start(1);
typeEnd = lineOffset + paramMatcher.end(1);
typeInfo = resolveType(typeName);
}
int paramNameStart = contentOffset + m.start(3);
int paramNameEnd = contentOffset + m.end(3);
int paramNameStart = -1;
int paramNameEnd = -1;
if (paramName != null) {
paramNameStart = lineOffset + paramMatcher.start(2);
paramNameEnd = lineOffset + paramMatcher.end(2);
}
JSDocParamTag tag = JSDocParamTag.create(atSignOffset, tagNameStart, tagNameEnd,
typeName, typeInfo, typeStart, typeEnd,
paramName, paramNameStart, paramNameEnd,
description != null ? description.trim() : null);
description);
info.addParamTag(tag);
return tag;
}
}
private void parseReturnTag(String content, int contentOffset, JSDocInfo info) {
Matcher m = RETURN_TAG_PATTERN.matcher(content);
if (m.find()) {
String tagName = m.group(1);
int atSignOffset = contentOffset + m.start();
int tagNameStart = contentOffset + m.start(1);
int tagNameEnd = contentOffset + m.end(1);
Matcher returnMatcher = RETURN_TAG_PATTERN.matcher(line);
if (returnMatcher.matches()) {
String tagName = returnMatcher.group(1);
String typeName = returnMatcher.group(2);
String description = normalizeDescription(returnMatcher.group(3));
String typeName = m.group(2);
String description = m.group(3);
int atSignOffset = lineOffset + returnMatcher.start();
int tagNameStart = lineOffset + returnMatcher.start(1);
int tagNameEnd = lineOffset + returnMatcher.end(1);
int typeStart = -1;
int typeEnd = -1;
@@ -172,41 +193,127 @@ public class JSDocParser {
if (typeName != null) {
typeName = typeName.trim();
int braceStart = content.indexOf('{', m.start());
int braceEnd = content.indexOf('}', braceStart);
typeStart = contentOffset + braceStart + 1;
typeEnd = contentOffset + braceEnd;
typeStart = lineOffset + returnMatcher.start(2);
typeEnd = lineOffset + returnMatcher.end(2);
typeInfo = resolveType(typeName);
}
JSDocReturnTag tag = JSDocReturnTag.create(tagName, atSignOffset, tagNameStart, tagNameEnd,
typeName, typeInfo, typeStart, typeEnd,
description != null ? description.trim() : null);
description);
info.setReturnTag(tag);
return tag;
}
Matcher typeMatcher = TYPE_TAG_PATTERN.matcher(line);
if (typeMatcher.matches()) {
String typeName = typeMatcher.group(2);
String description = normalizeDescription(typeMatcher.group(3));
int atSignOffset = lineOffset + typeMatcher.start();
int tagNameStart = lineOffset + typeMatcher.start(1);
int tagNameEnd = lineOffset + typeMatcher.end(1);
int typeStart = -1;
int typeEnd = -1;
TypeInfo typeInfo = null;
if (typeName != null) {
typeName = typeName.trim();
typeStart = lineOffset + typeMatcher.start(2);
typeEnd = lineOffset + typeMatcher.end(2);
typeInfo = resolveType(typeName);
}
JSDocTypeTag tag = JSDocTypeTag.create(atSignOffset, tagNameStart, tagNameEnd,
typeName, typeInfo, typeStart, typeEnd, description);
info.setTypeTag(tag);
return tag;
}
Matcher genericMatcher = GENERIC_TAG_PATTERN.matcher(line);
if (genericMatcher.matches()) {
String tagName = genericMatcher.group(1);
String typeName = genericMatcher.group(2);
String description = normalizeDescription(genericMatcher.group(3));
int atSignOffset = lineOffset + genericMatcher.start();
int tagNameStart = lineOffset + genericMatcher.start(1);
int tagNameEnd = lineOffset + genericMatcher.end(1);
JSDocTag tag = new JSDocTag(tagName, atSignOffset, tagNameStart, tagNameEnd);
if (typeName != null) {
typeName = typeName.trim();
int typeStart = lineOffset + genericMatcher.start(2);
int typeEnd = lineOffset + genericMatcher.end(2);
TypeInfo typeInfo = resolveType(typeName);
tag.setType(typeName, typeInfo, typeStart, typeEnd);
}
if (description != null && !description.isEmpty()) {
tag.setDescription(description);
}
info.addTag(tag);
return tag;
}
return null;
}
private void extractDescription(String content, JSDocInfo info) {
Matcher m = ANY_TAG_PATTERN.matcher(content);
String description;
private String normalizeDescription(String description) {
if (description == null) {
return null;
}
String trimmed = description.trim();
return trimmed.isEmpty() ? null : trimmed;
}
if (m.find()) {
description = content.substring(0, m.start());
} else {
description = content;
private CleanLine cleanLine(String line, int lineStartOffset) {
int index = 0;
while (index < line.length() && Character.isWhitespace(line.charAt(index))) {
index++;
}
description = description.replaceAll("(?m)^\\s*\\*\\s?", "").trim();
if (!description.isEmpty()) {
info.setDescription(description);
if (index + 2 < line.length() && line.startsWith("/**", index)) {
index += 3;
while (index < line.length() && Character.isWhitespace(line.charAt(index))) {
index++;
}
}
if (index < line.length() && line.charAt(index) == '*') {
index++;
if (index < line.length() && line.charAt(index) == ' ') {
index++;
}
}
String cleaned = line.substring(index);
int end = cleaned.length();
while (end > 0 && Character.isWhitespace(cleaned.charAt(end - 1))) {
end--;
}
cleaned = cleaned.substring(0, end);
return new CleanLine(cleaned, lineStartOffset + index);
}
private TypeInfo resolveType(String typeName) {
return document.resolveType(typeName);
}
private static class CleanLine {
private final String text;
private final int offset;
private CleanLine(String text, int offset) {
this.text = text;
this.offset = offset;
}
}
public static List<int[]> findAllJSDocComments(String source) {
List<int[]> positions = new ArrayList<>();
Matcher m = JSDOC_PATTERN.matcher(source);
@@ -1,30 +0,0 @@
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 + "}";
}
}