Skip to content

Commit a52f314

Browse files
Markdown Javadoc links to URLs not properly interpreted 4531
markdown links [description](url) syntax now interpreting properly Fix: #4531
1 parent f847cd8 commit a52f314

File tree

4 files changed

+158
-3
lines changed

4 files changed

+158
-3
lines changed

org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/parser/AbstractCommentParser.java

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1393,6 +1393,69 @@ protected boolean parseReference() throws InvalidInputException {
13931393
return parseReference(false);
13941394
}
13951395

1396+
// Checks if current identifier is a URL scheme (http, https, ftp, etc.)
1397+
private boolean isURLScheme(char[] identifier) {
1398+
return CharOperation.equals("http".toCharArray(), identifier) || //$NON-NLS-1$
1399+
CharOperation.equals("https".toCharArray(), identifier) || //$NON-NLS-1$
1400+
CharOperation.equals("ftp".toCharArray(), identifier) || //$NON-NLS-1$
1401+
CharOperation.equals("file".toCharArray(), identifier); //$NON-NLS-1$
1402+
}
1403+
1404+
// Checks if the current position is followed by "://" URL pattern
1405+
private boolean isFollowedByURLPattern() {
1406+
int pos = this.scanner.currentPosition;
1407+
int index = this.index;
1408+
try {
1409+
1410+
if (pos + 2 >= this.source.length) {
1411+
return false;
1412+
}
1413+
1414+
return this.source[pos] == ':' &&
1415+
this.source[pos + 1] == '/' &&
1416+
this.source[pos + 2] == '/';
1417+
} catch(Exception e) {
1418+
return false;
1419+
}
1420+
}
1421+
1422+
// Parses a complete URL reference starting from current position
1423+
private Object parseURLReference() throws InvalidInputException {
1424+
StringBuilder urlBuilder = new StringBuilder();
1425+
int firstTokenStartPos = this.scanner.startPosition;
1426+
//start with the current identifier
1427+
if (this.currentTokenType == TerminalToken.TokenNameIdentifier) {
1428+
urlBuilder.append(this.scanner.getCurrentTokenSource());
1429+
consumeToken();
1430+
}
1431+
1432+
// Add :
1433+
TerminalToken token1 = readTokenSafely();
1434+
if (token1 == TerminalToken.TokenNameCOLON) {
1435+
urlBuilder.append(this.scanner.getCurrentTokenSource());
1436+
consumeToken();
1437+
}
1438+
1439+
int pos = this.scanner.getCurrentTokenEndPosition() + 1;
1440+
char c;
1441+
while (pos < this.source.length) {
1442+
c = (this.source[pos] == ')') ? this.source[pos] : readChar();
1443+
1444+
if (c == ':') continue;
1445+
if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '*' || c == ')' || c == '(' ) {
1446+
break;
1447+
}
1448+
urlBuilder.append(c);
1449+
pos++;
1450+
}
1451+
char[] fullURL = urlBuilder.toString().toCharArray();
1452+
this.identifierPtr = 0;
1453+
this.identifierStack[this.identifierPtr] = fullURL;
1454+
this.identifierPositionStack[this.identifierPtr] = (((long) firstTokenStartPos) << 32) + (pos - 1);
1455+
this.identifierLengthStack[this.identifierLengthPtr] = 1;
1456+
return createTypeReference(TokenNameInvalid, true);
1457+
}
1458+
13961459
/*
13971460
* Parse a reference in @see tag
13981461
*/
@@ -1488,6 +1551,16 @@ else if (this.tagValue == TAG_VALUE_VALUE) {
14881551
break nextToken;
14891552
case TokenNameUNDERSCORE:
14901553
case TokenNameIdentifier :
1554+
char[] identifier = this.scanner.getCurrentIdentifierSource();
1555+
if (isURLScheme(identifier)) {
1556+
if (isFollowedByURLPattern()) {
1557+
if (typeRef == null) {
1558+
typeRefStartPosition = this.scanner.getCurrentTokenStartPosition();
1559+
typeRef = parseURLReference();
1560+
if (this.abort) return false;
1561+
}
1562+
}
1563+
}
14911564
if (typeRef == null) {
14921565
typeRefStartPosition = this.scanner.getCurrentTokenStartPosition();
14931566
typeRef = parseQualifiedName(true, allowModule);
@@ -3612,6 +3685,7 @@ protected boolean verifySpaceOrEndComment() {
36123685
// Whitespace or inline tag closing brace
36133686
char ch = peekChar();
36143687
switch (ch) {
3688+
case ')':
36153689
case ']':
36163690
// TODO: Check if we need to exclude escaped ]
36173691
if (this.markdown)

org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/parser/JavadocParser.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -573,7 +573,7 @@ protected boolean parseMarkdownLinks(int previousPosition) throws InvalidInputEx
573573
}
574574
break;
575575
case ']':
576-
if (peekChar() == '[') {
576+
if (peekChar() == '[' || peekChar() == '(') {
577577
// We might want to store the description in case of DOM parser
578578
// but the compiler does not need it
579579
//int length = this.index - start - 1;

org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/ASTConverterMarkdownTest.java

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1914,4 +1914,84 @@ public class Table {}
19141914
assertEquals("Incorrect TagElement", 1, (fragments.get(3).getFlags() & ASTNode.MALFORMED)); //MALFOUND flag
19151915
}
19161916
}
1917+
1918+
public void testMarkdownURLs4531_01() throws JavaModelException {
1919+
String source = """
1920+
/// @see [Ex Si](ex.com)
1921+
public class Markdown() {}
1922+
""";
1923+
this.workingCopies = new ICompilationUnit[1];
1924+
this.workingCopies[0] = getWorkingCopy("/Converter_25/src/markdown/gh3761/Markdown.java", source, null);
1925+
if (this.docCommentSupport.equals(JavaCore.ENABLED)) {
1926+
CompilationUnit compilUnit = (CompilationUnit) runConversion(this.workingCopies[0], true);
1927+
TypeDeclaration typedeclaration = (TypeDeclaration) compilUnit.types().get(0);
1928+
Javadoc javadoc = typedeclaration.getJavadoc();
1929+
TagElement tags = (TagElement) javadoc.tags().get(0);
1930+
assertEquals("fragments count does not match", 2, tags.fragments().size());
1931+
TagElement tagElement = (TagElement) tags.fragments().get(1);
1932+
List<?> tagFragments = tagElement.fragments();
1933+
assertTrue(tagFragments.get(0) instanceof TextElement);
1934+
assertTrue(tagFragments.get(1) instanceof QualifiedName);
1935+
}
1936+
}
1937+
1938+
public void testMarkdownURLs4531_02() throws JavaModelException {
1939+
String source = """
1940+
/// @see [Ex Si](http://ex.com)
1941+
public class Markdown() {}
1942+
""";
1943+
this.workingCopies = new ICompilationUnit[1];
1944+
this.workingCopies[0] = getWorkingCopy("/Converter_25/src/markdown/gh3761/Markdown.java", source, null);
1945+
if (this.docCommentSupport.equals(JavaCore.ENABLED)) {
1946+
CompilationUnit compilUnit = (CompilationUnit) runConversion(this.workingCopies[0], true);
1947+
TypeDeclaration typedeclaration = (TypeDeclaration) compilUnit.types().get(0);
1948+
Javadoc javadoc = typedeclaration.getJavadoc();
1949+
TagElement tags = (TagElement) javadoc.tags().get(0);
1950+
assertEquals("fragments count does not match", 2, tags.fragments().size());
1951+
TagElement tagElement = (TagElement) tags.fragments().get(1);
1952+
List<?> tagFragments = tagElement.fragments();
1953+
assertTrue(tagFragments.get(0) instanceof TextElement);
1954+
assertTrue(tagFragments.get(1) instanceof SimpleName);
1955+
}
1956+
}
1957+
1958+
public void testMarkdownURLs4531_03() throws JavaModelException {
1959+
String source = """
1960+
/// @see [Ex Si](https://ex.com/a)
1961+
public class Markdown() {}
1962+
""";
1963+
this.workingCopies = new ICompilationUnit[1];
1964+
this.workingCopies[0] = getWorkingCopy("/Converter_25/src/markdown/gh3761/Markdown.java", source, null);
1965+
if (this.docCommentSupport.equals(JavaCore.ENABLED)) {
1966+
CompilationUnit compilUnit = (CompilationUnit) runConversion(this.workingCopies[0], true);
1967+
TypeDeclaration typedeclaration = (TypeDeclaration) compilUnit.types().get(0);
1968+
Javadoc javadoc = typedeclaration.getJavadoc();
1969+
TagElement tags = (TagElement) javadoc.tags().get(0);
1970+
assertEquals("fragments count does not match", 2, tags.fragments().size());
1971+
TagElement tagElement = (TagElement) tags.fragments().get(1);
1972+
List<?> tagFragments = tagElement.fragments();
1973+
assertTrue(tagFragments.get(0) instanceof TextElement);
1974+
assertTrue(tagFragments.get(1) instanceof SimpleName);
1975+
}
1976+
}
1977+
1978+
public void testMarkdownURLs4531_04() throws JavaModelException {
1979+
String source = """
1980+
/// @see [Ex Si](https://www.ex.net/a)
1981+
public class Markdown() {}
1982+
""";
1983+
this.workingCopies = new ICompilationUnit[1];
1984+
this.workingCopies[0] = getWorkingCopy("/Converter_25/src/markdown/gh3761/Markdown.java", source, null);
1985+
if (this.docCommentSupport.equals(JavaCore.ENABLED)) {
1986+
CompilationUnit compilUnit = (CompilationUnit) runConversion(this.workingCopies[0], true);
1987+
TypeDeclaration typedeclaration = (TypeDeclaration) compilUnit.types().get(0);
1988+
Javadoc javadoc = typedeclaration.getJavadoc();
1989+
TagElement tags = (TagElement) javadoc.tags().get(0);
1990+
assertEquals("fragments count does not match", 2, tags.fragments().size());
1991+
TagElement tagElement = (TagElement) tags.fragments().get(1);
1992+
List<?> tagFragments = tagElement.fragments();
1993+
assertTrue(tagFragments.get(0) instanceof TextElement);
1994+
assertTrue(tagFragments.get(1) instanceof SimpleName);
1995+
}
1996+
}
19171997
}

org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/DocCommentParser.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -733,13 +733,14 @@ protected boolean parseMarkdownLinks(int previousPosition) throws InvalidInputEx
733733
readChar();
734734
}
735735
break;
736+
case ')':
736737
case ']':
737-
if (peekChar() == '[') {
738+
if ((peekChar() == '[' ) || peekChar() == '(') {
738739
tStart = start;
739740
tEnd = this.index - 1;
740741
currentChar = readChar();
741742
start = this.index;
742-
} else {
743+
} else if (peekChar() != ']') {
743744
int eofBkup = this.scanner.eofPosition;
744745
this.scanner.eofPosition = this.index - 1;
745746
this.scanner.resetTo(start, this.javadocEnd);

0 commit comments

Comments
 (0)