Skip to content

Commit e3dd6be

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 e3dd6be

File tree

4 files changed

+216
-3
lines changed

4 files changed

+216
-3
lines changed

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

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1393,6 +1393,65 @@ 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+
try {
1408+
if (pos + 2 >= this.source.length) return false;
1409+
1410+
return this.source[pos] == ':' &&
1411+
this.source[pos + 1] == '/' &&
1412+
this.source[pos + 2] == '/';
1413+
} catch(Exception e) {
1414+
return false;
1415+
}
1416+
}
1417+
1418+
// Parses a complete URL reference starting from current position
1419+
private Object parseURLReference() throws InvalidInputException {
1420+
StringBuilder urlBuilder = new StringBuilder();
1421+
int firstTokenStartPos = this.scanner.startPosition;
1422+
//start with the current identifier
1423+
if (this.currentTokenType == TerminalToken.TokenNameIdentifier) {
1424+
urlBuilder.append(this.scanner.getCurrentTokenSource());
1425+
consumeToken();
1426+
}
1427+
1428+
// Add :
1429+
TerminalToken token1 = readTokenSafely();
1430+
if (token1 == TerminalToken.TokenNameCOLON) {
1431+
urlBuilder.append(this.scanner.getCurrentTokenSource());
1432+
consumeToken();
1433+
}
1434+
1435+
int pos = this.scanner.getCurrentTokenEndPosition() + 1;
1436+
char c;
1437+
while (pos < this.source.length) {
1438+
c = (this.source[pos] == ')') ? this.source[pos] : readChar();
1439+
1440+
if (c == ':') continue;
1441+
if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '*' || c == ')' || c == '(' || c == '[' || c == ']' ) {
1442+
break;
1443+
}
1444+
urlBuilder.append(c);
1445+
pos++;
1446+
}
1447+
char[] fullURL = urlBuilder.toString().toCharArray();
1448+
this.identifierPtr = 0;
1449+
this.identifierStack[this.identifierPtr] = fullURL;
1450+
this.identifierPositionStack[this.identifierPtr] = (((long) firstTokenStartPos) << 32) + (pos - 1);
1451+
this.identifierLengthStack[this.identifierLengthPtr] = 1;
1452+
return createTypeReference(TokenNameInvalid, true);
1453+
}
1454+
13961455
/*
13971456
* Parse a reference in @see tag
13981457
*/
@@ -1488,6 +1547,16 @@ else if (this.tagValue == TAG_VALUE_VALUE) {
14881547
break nextToken;
14891548
case TokenNameUNDERSCORE:
14901549
case TokenNameIdentifier :
1550+
char[] identifier = this.scanner.getCurrentIdentifierSource();
1551+
if (isURLScheme(identifier)) {
1552+
if (isFollowedByURLPattern()) {
1553+
if (typeRef == null) {
1554+
typeRefStartPosition = this.scanner.getCurrentTokenStartPosition();
1555+
typeRef = parseURLReference();
1556+
if (this.abort) return false;
1557+
}
1558+
}
1559+
}
14911560
if (typeRef == null) {
14921561
typeRefStartPosition = this.scanner.getCurrentTokenStartPosition();
14931562
typeRef = parseQualifiedName(true, allowModule);
@@ -3612,6 +3681,7 @@ protected boolean verifySpaceOrEndComment() {
36123681
// Whitespace or inline tag closing brace
36133682
char ch = peekChar();
36143683
switch (ch) {
3684+
case ')':
36153685
case ']':
36163686
// TODO: Check if we need to exclude escaped ]
36173687
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: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1914,4 +1914,142 @@ 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+
}
1997+
1998+
public void testMarkdownURLs4531_05() throws JavaModelException {
1999+
String source = """
2000+
/// @see [Ex Si][http://ex.com]
2001+
public class Markdown() {}
2002+
""";
2003+
this.workingCopies = new ICompilationUnit[1];
2004+
this.workingCopies[0] = getWorkingCopy("/Converter_25/src/markdown/gh3761/Markdown.java", source, null);
2005+
if (this.docCommentSupport.equals(JavaCore.ENABLED)) {
2006+
CompilationUnit compilUnit = (CompilationUnit) runConversion(this.workingCopies[0], true);
2007+
TypeDeclaration typedeclaration = (TypeDeclaration) compilUnit.types().get(0);
2008+
Javadoc javadoc = typedeclaration.getJavadoc();
2009+
TagElement tags = (TagElement) javadoc.tags().get(0);
2010+
assertEquals("fragments count does not match", 2, tags.fragments().size());
2011+
TagElement tagElement = (TagElement) tags.fragments().get(1);
2012+
List<?> tagFragments = tagElement.fragments();
2013+
assertTrue(tagFragments.get(0) instanceof TextElement);
2014+
assertTrue(tagFragments.get(1) instanceof SimpleName);
2015+
}
2016+
}
2017+
2018+
// [)[) - invalid condition
2019+
public void testMarkdownURLs4531_06() throws JavaModelException {
2020+
String source = """
2021+
/// @see [Ex Si)[http://ex.com)
2022+
public class Markdown() {}
2023+
""";
2024+
this.workingCopies = new ICompilationUnit[1];
2025+
this.workingCopies[0] = getWorkingCopy("/Converter_25/src/markdown/gh3761/Markdown.java", source, null);
2026+
if (this.docCommentSupport.equals(JavaCore.ENABLED)) {
2027+
CompilationUnit compilUnit = (CompilationUnit) runConversion(this.workingCopies[0], true);
2028+
TypeDeclaration typedeclaration = (TypeDeclaration) compilUnit.types().get(0);
2029+
Javadoc javadoc = typedeclaration.getJavadoc();
2030+
TagElement tags = (TagElement) javadoc.tags().get(0);
2031+
assertEquals("fragments count does not match", 3, tags.fragments().size());
2032+
assertTrue(tags.fragments().get(0) instanceof TextElement);
2033+
assertTrue(tags.fragments().get(1) instanceof TextElement);
2034+
assertTrue(tags.fragments().get(2) instanceof TagElement);
2035+
}
2036+
}
2037+
2038+
// (](] - invalid condition
2039+
public void testMarkdownURLs4531_07() throws JavaModelException {
2040+
String source = """
2041+
/// @see (Ex Si](http://ex.com]
2042+
public class Markdown() {}
2043+
""";
2044+
this.workingCopies = new ICompilationUnit[1];
2045+
this.workingCopies[0] = getWorkingCopy("/Converter_25/src/markdown/gh3761/Markdown.java", source, null);
2046+
if (this.docCommentSupport.equals(JavaCore.ENABLED)) {
2047+
CompilationUnit compilUnit = (CompilationUnit) runConversion(this.workingCopies[0], true);
2048+
TypeDeclaration typedeclaration = (TypeDeclaration) compilUnit.types().get(0);
2049+
Javadoc javadoc = typedeclaration.getJavadoc();
2050+
TagElement tags = (TagElement) javadoc.tags().get(0);
2051+
assertEquals("fragments count does not match", 1, tags.fragments().size());
2052+
assertTrue(tags.fragments().get(0) instanceof TextElement);
2053+
}
2054+
}
19172055
}

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -733,13 +733,18 @@ protected boolean parseMarkdownLinks(int previousPosition) throws InvalidInputEx
733733
readChar();
734734
}
735735
break;
736+
case ')':
736737
case ']':
737-
if (peekChar() == '[') {
738+
if (this.source[start - 1] == '[' && currentChar == ')' &&
739+
peekChar() == '[' && this.source[this.scanner.eofPosition-2] == ')') {
740+
return false; //[)[) - invalid
741+
}
742+
if ((peekChar() == '[' ) || peekChar() == '(') {
738743
tStart = start;
739744
tEnd = this.index - 1;
740745
currentChar = readChar();
741746
start = this.index;
742-
} else {
747+
} else if (peekChar() != ']') {
743748
int eofBkup = this.scanner.eofPosition;
744749
this.scanner.eofPosition = this.index - 1;
745750
this.scanner.resetTo(start, this.javadocEnd);

0 commit comments

Comments
 (0)