diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/RunFormatterTests.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/RunFormatterTests.java index 74298dd2392..64d2daabf4a 100644 --- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/RunFormatterTests.java +++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/RunFormatterTests.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2005, 2015 IBM Corporation and others. + * Copyright (c) 2005, 2025 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -41,6 +41,7 @@ public class RunFormatterTests extends junit.framework.TestCase { TEST_SUITES.add(FormatterJavadocDontIndentTagsTests.class); TEST_SUITES.add(FormatterJavadocDontIndentTagsDescriptionTests.class); TEST_SUITES.add(FormatterOldBugsGistTests.class); + TEST_SUITES.add(FormatterMarkdownCommentsTests.class); // should always be the last one, to cleanup environment after messy tests TEST_SUITES.add(CleanupAfterSuiteTests.class); diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/formatter/FormatterMarkdownCommentsTests.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/formatter/FormatterMarkdownCommentsTests.java new file mode 100644 index 00000000000..1263e26cbe3 --- /dev/null +++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/formatter/FormatterMarkdownCommentsTests.java @@ -0,0 +1,884 @@ +/******************************************************************************* + * Copyright (c) 2026 IBM Corporation and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.jdt.core.tests.formatter; + +import junit.framework.Test; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; + +public class FormatterMarkdownCommentsTests extends FormatterCommentsTests { + + public static Test suite() { + return buildModelTestSuite(FormatterMarkdownCommentsTests.class); + } + + public FormatterMarkdownCommentsTests(String name) { + super(name); + } + + public void testMarkdownSpacingFormat() throws JavaModelException { + setComplianceLevel(CompilerOptions.VERSION_23); + String input = """ + class Mark { + /// @param param + /// @return int + public int sample(String param) { + return 0; + } + } + """; + String expected = """ + class Mark { + /// @param param + /// @return int + public int sample(String param) { + return 0; + } + } + """; + formatSource(input, expected); + } + + public void testMarkdownEmptyLinesBtwnDiffTags() throws JavaModelException { + setComplianceLevel(CompilerOptions.VERSION_23); + this.formatterPrefs.comment_insert_empty_line_between_different_tags = true; + String input = """ + class Mark { + /// @param param1 + /// @return int + public int sample(String param1) { + return 0; + } + } + """; + String expected = """ + class Mark { + /// @param param1 + ///\s + /// @return int + public int sample(String param1) { + return 0; + } + } + """; + formatSource(input, expected); + } + + public void testMarkdownUnorderedListTags() throws JavaModelException { + setComplianceLevel(CompilerOptions.VERSION_23); + String input = """ + /// ## Unordered Lists + /// - Item 1 + /// - Subitem 2.1 + /// - Subitem 2.2 + /// - Item3 + /// - sdd + class Mark { + } + """; + String expected = """ + /// ## Unordered Lists + /// - Item 1 + /// - Subitem 2.1 + /// - Subitem 2.2 + /// - Item3 + /// - sdd + class Mark { + } + """; + formatSource(input, expected); + } + + public void testMarkdownUnorderedListWithInvalidSpaces() throws JavaModelException { + setComplianceLevel(CompilerOptions.VERSION_23); + String input = """ + /// Invalid space + /// - Item A + /// - Item B + /// - Item C + /// - Item D + class RexTest {} + """; + String expected = """ + /// Invalid space + /// - Item A + /// - Item B + /// - Item C + /// - Item D + class RexTest { + } + """; + formatSource(input, expected); + } + + public void testMarkdownUnorderedListDifferentTags() throws JavaModelException { + setComplianceLevel(CompilerOptions.VERSION_23); + String input = """ + /// ## Unordered Lists + /// + Item 1 + /// * Item 2 + /// - Subitem 2.1 + /// + Subitem 2.2 + /// * Item 3 + class Mark { + } + """; + String expected = """ + /// ## Unordered Lists + /// + Item 1 + ///\s + /// * Item 2 + /// - Subitem 2.1 + ///\s + /// + Subitem 2.2 + /// * Item 3 + class Mark { + } + """; + formatSource(input, expected); + } + + public void testMarkdownOrderedListTags() throws JavaModelException { + setComplianceLevel(CompilerOptions.VERSION_23); + String input = """ + /// ## Ordered Lists + /// 1. First item + /// 2. Second item + /// 1. Subitem 2.1 + /// 2. Subitem 2.2 + /// * Sub Sub + /// 3. Third item + class Mark { + } + """; + String expected = """ + /// ## Ordered Lists + /// 1. First item + /// 2. Second item + /// 1. Subitem 2.1 + /// 2. Subitem 2.2 + /// * Sub Sub + /// 3. Third item + class Mark { + } + """; + formatSource(input, expected); + } + + public void testMarkdownHeadings() throws JavaModelException { + setComplianceLevel(CompilerOptions.VERSION_23); + String input = """ + /// ==== + /// # Heading 1 ## Heading 2 + /// ### Heading 3 #### Heading 4 + /// ##### Heading 5 + /// ###### Heading 6 + /// + /// Heading 1 + /// ========= + /// + /// Heading 2 --------- + class Mark { + } + """; + String expected = """ + /// ==== + ///\s + /// # Heading 1 ## Heading 2 + ///\s + /// ### Heading 3 #### Heading 4 + ///\s + /// ##### Heading 5 + ///\s + /// ###### Heading 6 + /// + /// Heading 1 + /// ========= + /// + /// Heading 2 --------- + class Mark { + } + """; + formatSource(input, expected); + } + + public void testMarkdownHeadingWithBody() throws JavaModelException { + setComplianceLevel(CompilerOptions.VERSION_23); + String input = """ + /// # heading + /// body content + class Mark { + } + """; + String expected = """ + /// # heading + /// body content + class Mark { + } + """; + formatSource(input, expected); + } + + public void testMarkdownSnippetComments() throws JavaModelException { + setComplianceLevel(CompilerOptions.VERSION_23); + String input = """ + /// Markdown Snippet + /// ``` + /// public class HelloWorld2 { public static void main(String... args) { + /// System.out.println("Hello World!"); // the traditional example + /// } + /// } + /// ``` + class Test2 { + } + """; + String expected = """ + /// Markdown Snippet + /// ``` + /// public class HelloWorld2 { + /// public static void main(String... args) { + /// System.out.println("Hello World!"); // the traditional example + /// } + /// } + /// ``` + class Test2 { + } + """; + formatSource(input, expected); + } + + public void testMarkdownMultiSnippetComments() throws JavaModelException { + setComplianceLevel(CompilerOptions.VERSION_23); + String input = """ + /// ``` + /// public class HelloWorld { + /// public static void main(String args) { System.out.println("Hello World!");// the traditional example + /// } + /// } + /// ``` + /// + /// ```````````` + /// public class HelloWorld2 {public static void main(String args) { System.out.println("ssdd World!");// the traditional example + /// } + /// } + /// ```````````` + class Test3 { + } + """; + String expected = """ + /// ``` + /// public class HelloWorld { + /// public static void main(String args) { + /// System.out.println("Hello World!");// the traditional example + /// } + /// } + /// ``` + /// + /// ```````````` + /// public class HelloWorld2 { + /// public static void main(String args) { + /// System.out.println("ssdd World!");// the traditional example + /// } + /// } + /// ```````````` + class Test3 { + } + """; + formatSource(input, expected); + } + + public void testMarkdownMultiSnippetCommentsWithoutCode() throws JavaModelException { + setComplianceLevel(CompilerOptions.VERSION_23); + String input = """ + /// ``` + /// This is a random sentence + /// inside snippet + /// ``` + class Test20i { + } + """; + String expected = """ + /// ``` + /// This is a random sentence + /// inside snippet + /// ``` + class Test20i { + } + """; + formatSource(input, expected); + } + + public void testMarkdownFencedCodeBlock() throws JavaModelException { + setComplianceLevel(CompilerOptions.VERSION_23); + String input = """ + /// A markdown comment, with a codeblock. + /// ```java + /// void foo() { + /// System.out.println("Hello, World!"); // 2 spaces indented + /// } + /// ``` + class Foo { } + """; + String expected = """ + /// A markdown comment, with a codeblock. + /// ```java + /// void foo() { + /// System.out.println("Hello, World!"); // 2 spaces indented + /// } + /// ``` + class Foo { + } + """; + formatSource(input, expected); + } + + public void testMarkdownFencedCodeBlockWithTilde() throws JavaModelException { + setComplianceLevel(CompilerOptions.VERSION_23); + String input = """ + /// Markdown tilde codeblock. + /// ~~~java + /// void foo() { + /// System.out.println("Hello, World!"); + /// } + /// ~~~ + class Foo { } + """; + String expected = """ + /// Markdown tilde codeblock. + /// ~~~java + /// void foo() { + /// System.out.println("Hello, World!"); + /// } + /// ~~~ + class Foo { + } + """; + formatSource(input, expected); + } + + public void testMarkdownFencedCodeFormatForValidOnes() throws JavaModelException { + setComplianceLevel(CompilerOptions.VERSION_23); + String input = """ + /// Markdown Snippets 1 + /// ``` + /// public class HelloWorld { + /// public static void main(String args) { + /// System.out.println("Hello World!"); + /// } + /// } + /// ``` + /// + /// Markdown Snippets 2 + /// + /// ~~~ + /// public class HelloWorld2 { + /// public static void main(String args) { ``` + /// System.out.println("ssdd World!"); + /// } + /// } + /// ~~~ + /// + /// Markdown Snippets 3 + /// + /// ``` + /// public class HelloWorld3 { + /// public static void main(String args) { + /// System.out.println("ssdd World!"); + /// } + /// } + /// ``` + class Test3 { + } + """; + String expected = """ + /// Markdown Snippets 1 + /// ``` + /// public class HelloWorld { + /// public static void main(String args) { + /// System.out.println("Hello World!"); + /// } + /// } + /// ``` + /// + /// Markdown Snippets 2 + /// + /// ~~~ + /// public class HelloWorld2 { + /// public static void main(String args) { ``` + /// System.out.println("ssdd World!"); + /// } + /// } + /// ~~~ + /// + /// Markdown Snippets 3 + /// + /// ``` + /// public class HelloWorld3 { + /// public static void main(String args) { + /// System.out.println("ssdd World!"); + /// } + /// } + /// ``` + class Test3 { + } + """; + formatSource(input, expected); + } + + public void testMarkdownTagLength() throws JavaModelException { + setComplianceLevel(CompilerOptions.VERSION_23); + String input = """ + /// hello + //// world + class m22 { + } + """; + String expected = """ + /// hello / world + class m22 { + } + """; + formatSource(input, expected); + } + + public void testMarkdownDoNotBreakSingleElementList() throws JavaModelException { + setComplianceLevel(CompilerOptions.VERSION_23); + String input = """ + /// - item A - item B - item C + class m22 { + } + """; + String expected = """ + /// - item A - item B - item C + class m22 { + } + """; + formatSource(input, expected); + } + + public void testMarkdownDoNotBreakTableOutsideClass() throws JavaModelException { + setComplianceLevel(CompilerOptions.VERSION_23); + String input = """ + /// | Latin | Greek | + /// |-------|-------| + /// | a | alpha | + /// | b | beta | + /// | c | gamma | + class Main {} + """; + String expected = """ + /// | Latin | Greek | + /// |-------|-------| + /// | a | alpha | + /// | b | beta | + /// | c | gamma | + class Main { + } + """; + formatSource(input, expected); + } + public void testMarkdownDoNotBreakMultipleTableInsideClass() throws JavaModelException { + setComplianceLevel(CompilerOptions.VERSION_23); + String input = """ + class Main { + /// | Latin | Greek | + /// |-------|-------| + /// | a | alpha | + /// + /// Hello Eclipse + /// + /// | Latin | Greek | + /// |-------|-------| + /// | a | alpha | + public void sample(String param1) {}} + """; + String expected = """ + class Main { + /// | Latin | Greek | + /// |-------|-------| + /// | a | alpha | + /// + /// Hello Eclipse + /// + /// | Latin | Greek | + /// |-------|-------| + /// | a | alpha | + public void sample(String param1) { + } + } + """; + formatSource(input, expected); + } + + public void testMarkdownDoNotBreakTwoListOfSameLevel() throws JavaModelException { + setComplianceLevel(CompilerOptions.VERSION_23); + String input = """ + /// 1. one + /// 1. two + class Mark62 {} + """; + String expected = """ + /// 1. one + /// 1. two + class Mark62 { + } + """; + formatSource(input, expected); + } + + public void testMarkdownDoNotFormatInvalidSerialNumberLists() throws JavaModelException { + setComplianceLevel(CompilerOptions.VERSION_23); + String input = """ + /// something + /// 2. one + /// 2. two + class Mark62 { + } + """; + String expected = """ + /// something 2. one 2. two + class Mark62 { + } + """; + formatSource(input, expected); + } + + public void testMarkdownInvalidNestedSerialNumberedLists() throws JavaModelException { + setComplianceLevel(CompilerOptions.VERSION_23); + String input = """ + /// 1. one + /// 2. two + class Mark62 { + } + """; + String expected = """ + /// 1. one 2. two + class Mark62 { + } + """; + formatSource(input, expected); + } + + public void testMarkdownInvalidLargeIndentation() throws JavaModelException { + setComplianceLevel(CompilerOptions.VERSION_23); + String input = """ + /// 1. one + /// 1. two + /// - one + /// - two + class Mark62 { + } + """; + String expected = """ + /// 1. one 1. two + ///\s + /// - one - two + class Mark62 { + } + """; + formatSource(input, expected); + } + + public void testMarkdownOrderedListWithParanthesis() throws JavaModelException { + setComplianceLevel(CompilerOptions.VERSION_23); + String input = """ + /// ## Ordered Lists + /// 1) First item + /// 2) Second item + /// 1) Subitem 2.1 + /// 2) Subitem 2.2 + /// 2) Subitem 2.2 + /// * Sub Sub + /// 3) Third item + class Mark62 { + } + """; + String expected = """ + /// ## Ordered Lists + /// 1) First item + ///\s + /// 2) Second item + /// 1) Subitem 2.1 + /// 2) Subitem 2.2 2) Subitem 2.2 + /// * Sub Sub + /// 3) Third item + class Mark62 { + } + """; + formatSource(input, expected); + } + + public void testMarkdownOrderedListWithLineBreaksOnDifferentChars() throws JavaModelException { + setComplianceLevel(CompilerOptions.VERSION_23); + String input = """ + /// ## Ordered Lists + /// 1. First item + /// 1) Second item + /// 1. Subitem 2.1 + /// 2. Subitem 2.2 + /// 3) Third item + class Mark62 { + } + """; + String expected = """ + /// ## Ordered Lists + /// 1. First item + /// 1) Second item + ///\s + /// 1. Subitem 2.1 + /// 2. Subitem 2.2 + /// 3) Third item + class Mark62 { + } + """; + formatSource(input, expected); + } + + public void testMarkdownListResetNestingOrderOnDIfferentTagInBetween() throws JavaModelException { + setComplianceLevel(CompilerOptions.VERSION_23); + String input = """ + /// 1) Item 1 + /// 1) Sub Item 1 + /// 2) Sub Item 1 + /// # OR + /// 1. Item 1 + /// 1) Sub Item 1 + /// 2) Sub Item 1 + class Mark62 { + } + """; + String expected = """ + /// 1) Item 1 + /// 1) Sub Item 1 + /// 2) Sub Item 1 + ///\s + /// # OR + /// 1. Item 1 + /// 1) Sub Item 1 + /// 2) Sub Item 1 + class Mark62 { + } + """; + formatSource(input, expected); + } + + public void testMarkdownWrappedListItemIndentation() throws JavaModelException { + setComplianceLevel(CompilerOptions.VERSION_23); + this.formatterPrefs.comment_line_length = 25; + String input = """ + /// 1) Item 1 aaa bbb ccc ddd eee fff + /// 1) Sub Item 1 aaa bbb ccc ddd eee fff + /// 1) Item 2 aaa bbb ccc ddd eee fff + class Mark62 { + } + """; + String expected = """ + /// 1) Item 1 aaa bbb ccc + /// ddd eee fff + /// 1) Sub Item 1 aaa + /// bbb ccc ddd eee + /// fff + /// 1) Item 2 aaa bbb ccc + /// ddd eee fff + class Mark62 { + } + """; + formatSource(input, expected); + } + + public void testMarkdownIndentedCodeBlocks() throws JavaModelException { + setComplianceLevel(CompilerOptions.VERSION_23); + this.formatterPrefs.comment_line_length = 25; + String input = """ + /// Example: simple indented code block + /// + /// a simple + /// indented code block + class Test1 { } + """; + String expected = """ + /// Example: simple + /// indented code block + /// + /// a simple + /// indented code block + class Test1 { + } + """; + formatSource(input, expected); + } + + public void testMarkdownIndentedCodeBlocksMultiLineJava() throws JavaModelException { + setComplianceLevel(CompilerOptions.VERSION_23); + this.formatterPrefs.comment_line_length = 25; + String input = """ + /// Example: multi-line java + /// + /// for (int i = 0; i < 3; i++) { + /// System.out.println(i); + /// } + class Test3 { } + """; + String expected = """ + /// Example: multi-line + /// java + /// + /// for (int i = 0; i < 3; i++) { + /// System.out.println(i); + /// } + class Test3 { + } + """; + formatSource(input, expected); + } + + public void testMarkdownIndentedCodeBlocksFakeFences() throws JavaModelException { + setComplianceLevel(CompilerOptions.VERSION_23); + this.formatterPrefs.comment_line_length = 25; + String input = """ + /// Example: fake fenced inside indented + /// + /// ``` java + /// int x = 10; + /// System.out.println (x); + /// ``` + class TestFakeFences { } + """; + String expected = """ + /// Example: fake fenced + /// inside indented + /// + /// ``` java + /// int x = 10; + /// System.out.println (x); + /// ``` + class TestFakeFences { + } + """; + formatSource(input, expected); + } + + public void testMarkdownUnclosedCodeFences() throws JavaModelException { + setComplianceLevel(CompilerOptions.VERSION_23); + String input = """ + /// ``` + /// public class HelloWorld3 { + /// public static void main(String args) { + /// System.out.println("ssdd World!"); + /// } + /// } + class Mark61 { + } + """; + String expected = """ + /// ``` + /// public class HelloWorld3 { + /// public static void main(String args) { + /// System.out.println("ssdd World!"); + /// } + /// } + class Mark61 { + } + """; + formatSource(input, expected); + } + + public void testMarkdownFencedCodeInList() throws JavaModelException { + setComplianceLevel(CompilerOptions.VERSION_23); + String input = """ + /// 1. three four + /// 2. ``` + /// hello there + /// ``` + /// 2. one two + class Test { + } + """; + String expected = """ + /// 1. three four + /// 2. ``` + /// hello there + /// ``` + /// 2. one two + class Test { + } + """; + formatSource(input, expected); + } + + public void testMarkdownFencedCodeInListWithClosingFenceTooFar() throws JavaModelException { + setComplianceLevel(CompilerOptions.VERSION_23); + String input = """ + /// 1. three four + /// 2. ``` + /// hello there + /// ``` + /// 2. one two + class Test { + } + """; + String expected = """ + /// 1. three four + /// 2. ``` + /// hello there + /// ``` + /// 2. one two + class Test { + } + """; + formatSource(input, expected); + } + + public void testMarkdownFencedCodeInListEndedByNextListItem() throws JavaModelException { + setComplianceLevel(CompilerOptions.VERSION_23); + String input = """ + /// 1. three four + /// 2. ``` + /// hello there + /// ``` + /// 2. one two + class Test { + } + """; + String expected = """ + /// 1. three four + /// 2. ``` + /// hello there + /// ``` + /// 2. one two + class Test { + } + """; + formatSource(input, expected); + } + + public void testMarkdownWithEmptyLastLine() throws JavaModelException { + setComplianceLevel(CompilerOptions.VERSION_23); + String input = """ + /// aaa + /// bbb + ///\s + class Mark61 { + } + """; + String expected = """ + /// aaa bbb + ///\s + class Mark61 { + } + """; + formatSource(input, expected); + } + +} \ No newline at end of file diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/formatter/FormatterRegressionTests.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/formatter/FormatterRegressionTests.java index 0930b681807..aa6cc3d221a 100644 --- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/formatter/FormatterRegressionTests.java +++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/formatter/FormatterRegressionTests.java @@ -16490,52 +16490,6 @@ void main() { """, CodeFormatter.K_COMPILATION_UNIT); } - public void testMarkdownSpacingFormat() throws JavaModelException { - setComplianceLevel(CompilerOptions.VERSION_23); - String input = """ - class Mark { - /// @param param - /// @return int - public int sample(String param) { - return 0; - } - } - """; - String expected = """ - class Mark { - /// @param param - /// @return int - public int sample(String param) { - return 0; - } - } - """; - formatSource(input, expected); - } - public void testMarkdownEmptyLinesBtwnDiffTags() throws JavaModelException { - setComplianceLevel(CompilerOptions.VERSION_23); - this.formatterPrefs.comment_insert_empty_line_between_different_tags = true; - String input = """ - class Mark { - /// @param param1 - /// @return int - public int sample(String param1) { - return 0; - } - } - """; - String expected = """ - class Mark { - /// @param param1 - ///\s - /// @return int - public int sample(String param1) { - return 0; - } - } - """; - formatSource(input, expected); - } public void testRecordWithOpeningParanthesisInParam() throws JavaModelException { setComplianceLevel(CompilerOptions.VERSION_16); diff --git a/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/CommentsPreparator.java b/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/CommentsPreparator.java index dc4bee9772f..b0db23c3162 100644 --- a/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/CommentsPreparator.java +++ b/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/CommentsPreparator.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2014, 2025 Mateusz Matela and others. + * Copyright (c) 2014, 2026 Mateusz Matela and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -29,6 +29,7 @@ import static org.eclipse.jdt.internal.compiler.parser.TerminalToken.TokenNamepackage; import static org.eclipse.jdt.internal.formatter.TokenManager.ANY; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -38,15 +39,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; -import org.eclipse.jdt.core.dom.ASTNode; -import org.eclipse.jdt.core.dom.ASTVisitor; -import org.eclipse.jdt.core.dom.BlockComment; -import org.eclipse.jdt.core.dom.Javadoc; -import org.eclipse.jdt.core.dom.LineComment; -import org.eclipse.jdt.core.dom.MemberRef; -import org.eclipse.jdt.core.dom.MethodRef; -import org.eclipse.jdt.core.dom.QualifiedName; -import org.eclipse.jdt.core.dom.TagElement; +import org.eclipse.jdt.core.dom.*; import org.eclipse.jdt.core.formatter.DefaultCodeFormatterConstants; import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; import org.eclipse.jdt.internal.compiler.parser.ScannerHelper; @@ -99,6 +92,13 @@ public class CommentsPreparator extends ASTVisitor { SNIPPET_MARKUP_TAG_PATTERN = Pattern.compile(markupTagNames + markupTagArgument + "*"); //$NON-NLS-1$ } + private final static Pattern MARKDOWN_LEADING_SPACES_PATTERN = Pattern.compile("(?m)^[ \\t]*///( *)[^ ]"); //$NON-NLS-1$ + private final static Pattern MARKDOWN_HEADINGS_PATTERN_1 = Pattern.compile("(?:(?<=^)|(?<=///[ \\t]*))(#{1,6})([ \\t]+)([^\\r\\n]*)"); //$NON-NLS-1$ + private final static Pattern MARKDOWN_HEADINGS_PATTERN_2 = Pattern.compile("(?:^|(?<=///[ \\t]+))[ \\t]*([=-])\\1*[ \\t]*(?=\\r?\\n|$)"); //$NON-NLS-1$ + private final static Pattern MARKDOWN_FENCE_PATTERN = Pattern.compile("(`{3,}|~{3,})(.*)"); //$NON-NLS-1$ + private final static Pattern MARKDOWN_TABLE_START = Pattern.compile("(?m)(?<=^[ \\t]*)\\|"); //$NON-NLS-1$ + private final static Pattern MARKDOWN_TABLE_END = Pattern.compile("(?m)\\|(?!.*\\|)"); //$NON-NLS-1$ + // Param tags list copied from IJavaDocTagConstants in legacy formatter for compatibility. // There were the following comments: // TODO (frederic) should have another name than 'param' for the following tags @@ -644,7 +644,7 @@ public boolean visit(TagElement node) { if (startIndex > 1) { this.ctm.get(startIndex).breakBefore(); } - + handleMarkdown(node); handleHtml(node); this.ctm.get(tokenStartingAt(node.getStartPosition())).setToEscape(false); @@ -666,6 +666,7 @@ public boolean visit(TagElement node) { public void endVisit(TagElement node) { String tagName = node.getTagName(); if (tagName == null || tagName.length() <= 1) { + handleMarkdown(node); handleHtml(node); } else if (TagElement.TAG_SEE.equals(tagName)) { handleStringLiterals(this.tm.toString(node), node.getStartPosition()); @@ -793,6 +794,247 @@ private void alignJavadocTag(List tagTokens, int paramNameAlign, int desc } } + private void handleMarkdown(TagElement node) { + if (!(node.getParent() instanceof Javadoc javaDoc) || !javaDoc.isMarkdown() + || !this.options.comment_format_markdown_comment) { + return; + } + + String text = this.tm.toString(node); + + Matcher matcher = MARKDOWN_HEADINGS_PATTERN_1.matcher(text); // Check for MarkDown headings #h1 - #h6 + while (matcher.find()) { + int startPos = matcher.start() + node.getStartPosition(); + int tokenIndex = tokenStartingAt(startPos); + Token headingToken = this.ctm.get(tokenIndex); + if (tokenIndex != 1) { + headingToken.putLineBreaksBefore(2); + } + int endPos = matcher.end() + node.getStartPosition(); + int endIndex = tokenEndingAt(endPos - 1); + Token endingToken = this.ctm.get(endIndex); + endingToken.breakAfter(); + headingToken.spaceBefore(); + } + + matcher = MARKDOWN_HEADINGS_PATTERN_2.matcher(text); // Check for MarkDown headings with styles '-- & ===' + while (matcher.find()) { + int startPos = matcher.start() + node.getStartPosition(); + int tokenIndex = tokenStartingAt(startPos); + Token headingToken = this.ctm.get(tokenIndex); + if (tokenIndex != 1) { + headingToken.breakBefore(); + } + headingToken.breakAfter(); + } + + int leadingSpaces = Integer.MAX_VALUE; + Matcher m = MARKDOWN_LEADING_SPACES_PATTERN.matcher(this.tm.toString(node)); + while (m.find()) + leadingSpaces = Math.min(leadingSpaces, m.group(1).length()); + leadingSpaces = leadingSpaces == Integer.MAX_VALUE ? 0 : leadingSpaces; + List fragments = node.fragments(); + ArrayDeque fragmentsDeque = new ArrayDeque<>(fragments); + handleMarkdownList(fragmentsDeque, leadingSpaces, 0, null, 4000 - leadingSpaces); + handleMarkdownTable(fragments); + } + + private Character handleMarkdownList(ArrayDeque fragments, int srcIndent, int indentToSet, + Character itemType, int tabStartShift) { + Character prevRecursionItemType = null; + while (!fragments.isEmpty()) { + if (handleMarkdownIndentedCodeBlock(fragments, srcIndent, false, tabStartShift)) { + continue; + } + ASTNode fragment = fragments.peekFirst(); + int firstTokenIndex = this.ctm.findIndex(fragment.getStartPosition(), ANY, true); + Token firstToken = this.ctm.get(firstTokenIndex); + + String firstString = this.ctm.toString(firstToken); + Character thisItemType = null; + boolean isBullet = false; + if (fragment instanceof TextElement && (firstTokenIndex < 2 + || this.ctm.countLineBreaksBetween(this.ctm.get(firstTokenIndex - 1), firstToken) > 0)) { + isBullet = firstString.matches("[-+*]"); //$NON-NLS-1$ + if (isBullet) { + thisItemType = firstString.charAt(0); + } else if (firstString.matches("\\d+[.)]")) { //$NON-NLS-1$ + thisItemType = firstString.charAt(firstString.length() - 1); + } + } + + int slashPos = this.ctm.getSource().lastIndexOf('/', firstToken.originalStart); + int fragmentIndent = this.ctm.getLength(slashPos + 1, firstToken.originalStart - 1, tabStartShift, 4); + int indentDiff = fragmentIndent - srcIndent; + if (thisItemType != null) { + Token secondToken = this.ctm.get(firstTokenIndex + 1); + int textIndent = this.ctm.getLength(slashPos + 1, secondToken.originalStart - 1, tabStartShift, 4); + indentDiff = textIndent - srcIndent; + boolean isFirst = isBullet || (firstString.length() == 2 && firstString.charAt(0) == '1'); + boolean isNextLevel = (indentDiff > 1 && fragmentIndent >= srcIndent) || itemType == null; + if (fragmentIndent - srcIndent >= 4) { + thisItemType = null; + indentDiff = fragmentIndent - srcIndent; + } else if (isNextLevel) { + if (isFirst || thisItemType.equals(prevRecursionItemType)) { + prevRecursionItemType = handleMarkdownList(fragments, textIndent, + indentToSet + this.ctm.getLength(firstToken, 0) + 1, thisItemType, tabStartShift); + continue; + } else { + thisItemType = null; + indentDiff = fragmentIndent - srcIndent; + } + } else { + if (thisItemType.equals(itemType)) { + srcIndent = textIndent; + } else if (isFirst) { + firstToken.putLineBreaksBefore(2); + } + } + } + + if (indentDiff < 0 && itemType != null && (thisItemType != null || firstToken.getLineBreaksBefore() > 1)) { + return thisItemType; + } + + if (itemType == null) { + fragments.removeFirst(); + continue; + } + int lastTokenIndex = this.ctm.findIndex(fragment.getStartPosition() + fragment.getLength() - 1, ANY, false); + for (int i = firstTokenIndex; i <= lastTokenIndex; i++) + this.ctm.get(i).setAlign(indentToSet); + if (thisItemType != null) { + if (firstTokenIndex > 1) + firstToken.breakBefore(); + firstToken.setAlign(indentToSet - (isBullet ? 2 : 3)); + if (handleMarkdownIndentedCodeBlock(fragments, srcIndent, true, tabStartShift)) { + continue; + } + } + fragments.removeFirst(); + } + return null; + } + + private boolean handleMarkdownIndentedCodeBlock(ArrayDeque fragments, int srcIndent, boolean isListItem, + int tabStartShift) { + int codeBlockStartIndex = -1; + int codeBlockEndIndex = -1; + String openingFence = null; + boolean canAssumeJava = false; + while (!fragments.isEmpty()) { + ASTNode fragment = fragments.peekFirst(); + int firstTokenIndex = this.ctm.findIndex(fragment.getStartPosition(), ANY, true); + int lastTokenIndex = this.ctm.findIndex(fragment.getStartPosition() + fragment.getLength() - 1, ANY, false); + Token firstToken = this.ctm.get(firstTokenIndex); + int slashPos = this.ctm.getSource().lastIndexOf('/', firstToken.originalStart); + int fragmentIndent = this.ctm.getLength(slashPos + 1, firstToken.originalStart - 1, tabStartShift, 4); + int indentDiff = fragmentIndent - srcIndent; + Matcher fenceMatcher = MARKDOWN_FENCE_PATTERN.matcher(this.ctm.toString(fragment).trim()); + + if (codeBlockStartIndex == -1) { + if (isListItem) { + srcIndent = this.ctm.getLength(slashPos + 1, firstToken.originalEnd, tabStartShift); + firstTokenIndex++; + firstToken = this.ctm.get(firstTokenIndex); + fragmentIndent = this.ctm.getLength(slashPos + 1, firstToken.originalStart - 1, tabStartShift, 4); + indentDiff = fragmentIndent - srcIndent - 1; // somehow indent threshold is 1 higher in lists + fenceMatcher = MARKDOWN_FENCE_PATTERN.matcher(this.ctm.getSource() + .substring(firstToken.originalStart, fragment.getStartPosition() + fragment.getLength())); + } + + if (indentDiff >= 4 && (firstToken.getLineBreaksBefore() > 1 || isListItem)) { + codeBlockStartIndex = firstTokenIndex; + firstToken.setAlign(fragmentIndent - 1); + } else { + boolean isLineStart = isListItem || firstTokenIndex <= 1 + || this.ctm.countLineBreaksBetween(this.ctm.get(firstTokenIndex - 1), firstToken) > 0; + if (isLineStart && fenceMatcher.matches()) { + codeBlockStartIndex = lastTokenIndex; + openingFence = fenceMatcher.group(1); + String infoString = fenceMatcher.group(2).trim().toLowerCase(); + canAssumeJava = infoString.isEmpty() || infoString.equals("java"); //$NON-NLS-1$ + if (firstTokenIndex > 1 && !isListItem) + firstToken.breakBefore(); + if (codeBlockStartIndex < this.ctm.size() - 1) + this.ctm.get(codeBlockStartIndex).breakAfter(); + } else { + return false; + } + } + } else if (indentDiff <= 4) { + if (openingFence != null) { + if (fenceMatcher.matches() && openingFence.equals(fenceMatcher.group(1)) && fenceMatcher.group(2).trim().isEmpty()) { + codeBlockEndIndex = firstTokenIndex - 1; + firstToken.breakBefore(); + if (firstTokenIndex < this.tm.size()) + firstToken.breakAfter(); + firstToken.setAlign(fragmentIndent - 1); + fragments.removeFirst(); + break; + } else if (indentDiff < 0) { + codeBlockEndIndex = firstTokenIndex - 1; + break; + } + } else { + firstToken.putLineBreaksBefore(2); + break; + } + } + codeBlockEndIndex = lastTokenIndex; + fragments.removeFirst(); + } + + if (canAssumeJava && codeBlockStartIndex < codeBlockEndIndex + && formatCode(codeBlockStartIndex, codeBlockEndIndex + 1, false, true)) { + // code formatting successful + } else if (openingFence != null && codeBlockStartIndex < codeBlockEndIndex) { + disableFormattingExclusively(codeBlockStartIndex, codeBlockEndIndex + 1); + } else if (codeBlockStartIndex != -1) { + disableFormatting(codeBlockStartIndex, codeBlockEndIndex, true); + } + return codeBlockStartIndex != -1; + } + + private void handleMarkdownTable(List fragments) { + int tableStartIndex = -1; + int tableLastIndex = -1; + boolean columnUnderlineFound = false; + Matcher matcher; + for (Object fragment : fragments) { + if (fragment instanceof TextElement textElement) { + String textContent = textElement.getText(); + matcher = MARKDOWN_TABLE_START.matcher(textContent); + if (matcher.find()) { + if (tableStartIndex == -1) { + int startPos = matcher.start() + textElement.getStartPosition(); + tableStartIndex = tokenStartingAt(startPos); + } else if (tableStartIndex != -1 && !columnUnderlineFound) { + boolean foundStart = textContent.contains("|-"); //$NON-NLS-1$ + boolean foundEnd = textContent.contains("-|"); //$NON-NLS-1$ + if (foundStart && foundEnd) { + columnUnderlineFound = true; + } + } else if (columnUnderlineFound) { + matcher = MARKDOWN_TABLE_END.matcher(textContent); + matcher.find(); // find the last one + int startPos = matcher.start() + textElement.getStartPosition(); + tableLastIndex = tokenStartingAt(startPos); + } + } else { + tableStartIndex = -1; + tableLastIndex = -1; + columnUnderlineFound = false; + } + } + if (tableStartIndex != -1 && tableLastIndex != -1) { + // TODO fix column alignment and format cells + disableFormattingExclusively(tableStartIndex, tableLastIndex); + } + } + } + private void handleHtml(TagElement node) { if (!this.options.comment_format_html && !this.options.comment_format_source) return; @@ -1031,7 +1273,7 @@ private void handleFormatCodeTag(TagElement tagElement, int startPos, int endPos } closingBracePos--; - if (formatCode(tokenEndingAt(nameEndPos), tokenStartingAt(closingBracePos), false)) { + if (formatCode(tokenEndingAt(nameEndPos), tokenStartingAt(closingBracePos), false, false)) { int closingIndex = tokenStartingAt(closingBracePos); Token t = this.ctm.get(closingIndex); this.commentStructure.set(closingIndex, @@ -1054,17 +1296,12 @@ private void handleFormatCodeTag(TagElement tagElement, int startPos, int endPos } } } else if (this.formatCodeOpenTagEndIndex >= startIndex - 1 - || !formatCode(this.formatCodeOpenTagEndIndex, startIndex, false)) { + || !formatCode(this.formatCodeOpenTagEndIndex, startIndex, false, false)) { disableFormattingExclusively(this.formatCodeOpenTagEndIndex, startIndex); } this.formatCodeOpenTagEndIndex = -1; this.lastFormatCodeClosingTagIndex = this.ctm.findIndex(startPos, ANY, true); } -// if (!isOpeningTag) { -// if (this.options.comment_javadoc_do_not_separate_block_tags) { -// handleBreakAfterTag(startIndex, endTagIndex); -// } -// } } private void handleSnippet(TagElement node, int startIndex, int endIndex) { @@ -1094,7 +1331,7 @@ private void handleSnippet(TagElement node, int startIndex, int endIndex) { int closingIndex = tokenStartingAt(endToken.originalEnd); this.ctm.get(closingIndex).breakBefore(); boolean formatted = (lang == null || lang.matches("['\"]?java['\"]?")) //$NON-NLS-1$ - && formatCode(openingIndex, closingIndex, true); + && formatCode(openingIndex, closingIndex, true, false); if (!formatted) disableFormattingExclusively(openingIndex, closingIndex); } @@ -1167,7 +1404,8 @@ private int findCommentLineIndent(int commentFragmentIndex) { if (!ScannerHelper.isWhitespace(c)) lastNonWhitespace = position; } - if (lastNonWhitespace > 0 && this.ctm.charAt(lastNonWhitespace - 1) == ' ') + if (this.ctm.get(commentFragmentIndex).tokenType != TokenNameCOMMENT_MARKDOWN && lastNonWhitespace > 0 + && this.ctm.charAt(lastNonWhitespace - 1) == ' ') lastNonWhitespace--; return this.ctm.getLength(position, lastNonWhitespace - 1, 0); } @@ -1218,7 +1456,7 @@ private boolean tokenizeMultilineComment(Token commentToken) { } boolean isMarkdown = commentToken.tokenType == TokenNameCOMMENT_MARKDOWN; boolean isJavadoc = commentToken.tokenType == TokenNameCOMMENT_JAVADOC; - Arrays.fill(this.allowSubstituteWrapping, 0, commentToken.countChars(), !isJavadoc); + Arrays.fill(this.allowSubstituteWrapping, 0, commentToken.countChars(), !isMarkdown && !isJavadoc); final boolean cleanBlankLines = isJavadoc ? this.options.comment_clear_blank_lines_in_javadoc_comment : this.options.comment_clear_blank_lines_in_block_comment; @@ -1250,15 +1488,18 @@ private boolean tokenizeMultilineComment(Token commentToken) { i++; position = i + 1; } else if (!ScannerHelper.isWhitespace(c)) { - while (this.tm.charAt(i) == markerChar && lineBreaks > 0) + int markerCharCount = 0; + while (this.tm.charAt(i) == markerChar && lineBreaks > 0 && (markerCharCount < 3 || !isMarkdown)) { + markerCharCount++; i++; + } position = i; break; } } int tokenStart = position; - while (position <= commentToken.originalEnd + 1) { + while (position <= commentToken.originalEnd + 1 && (!isMarkdown || position <= positionLimit)) { char c = 0; if (position == commentToken.originalEnd + 1 || position == positionLimit || ScannerHelper.isWhitespace(c = this.tm.charAt(position))) { @@ -1295,10 +1536,14 @@ private boolean tokenizeMultilineComment(Token commentToken) { } position++; } + if (isMarkdown && position > positionLimit) + break; } Token last = structure.get(structure.size() - 1); - if (!isMarkdown) { + if (isMarkdown) { + last.putLineBreaksAfter(lineBreaks); + } else { boolean newLinesAtBoundries = commentToken.tokenType == TokenNameCOMMENT_JAVADOC ? this.options.comment_new_lines_at_javadoc_boundaries : this.options.comment_new_lines_at_block_boundaries; @@ -1351,13 +1596,14 @@ private void addSubstituteWraps() { } } - private boolean formatCode(int openingIndex, int closingIndex, boolean snippetTag) { + private boolean formatCode(int openingIndex, int closingIndex, boolean snippetTag, boolean isMarkdown) { int codeStartPosition = this.ctm.get(openingIndex).originalEnd + 1; - int codeEndPosition = this.ctm.get(closingIndex).originalStart - 1; + int codeEndPosition = closingIndex < this.ctm.size() ? this.ctm.get(closingIndex).originalStart - 1 + : this.ctm.get(this.ctm.size() - 1).originalEnd; StringBuilder codeBuilder = new StringBuilder(codeEndPosition - codeStartPosition + 1); int[] positionMapping = new int[codeEndPosition - codeStartPosition + 1]; // ^ index: original source position (minus startPosition), value: position in code string - getCodeToFormat(codeStartPosition, codeEndPosition, codeBuilder, positionMapping); + getCodeToFormat(codeStartPosition, codeEndPosition, codeBuilder, positionMapping, isMarkdown); DefaultCodeFormatter formatter = snippetTag ? getSnippetCodeFormatter() : getPreTagCodeFormatter(); List formattedTokens = formatter.prepareFormattedCode(codeBuilder.toString()); @@ -1376,11 +1622,12 @@ private boolean formatCode(int openingIndex, int closingIndex, boolean snippetTa // there are too few linebreaks at the start and end Token start = formattedTokens.get(0); start.putLineBreaksBefore(start.getLineBreaksBefore() + 1); - Token end = formattedTokens.get(formattedTokens.size() - 1); - end.putLineBreaksAfter(end.getLineBreaksAfter() + 1); - // and there may be too many line breaks before closing tag - this.ctm.get(closingIndex).clearLineBreaksBefore(); - + if (!isMarkdown) { + Token end = formattedTokens.get(formattedTokens.size() - 1); + end.putLineBreaksAfter(end.getLineBreaksAfter() + 1); + // and there may be too many line breaks before closing tag + this.ctm.get(closingIndex).clearLineBreaksBefore(); + } List tokensToReplace = this.commentStructure.subList(openingIndex + 1, closingIndex); tokensToReplace.clear(); tokensToReplace.addAll(formattedTokens); @@ -1418,7 +1665,7 @@ private DefaultCodeFormatter getSnippetCodeFormatter() { return this.snippetCodeFormatter; } - private void getCodeToFormat(int startPos, int endPos, StringBuilder sb, int[] posMapping) { + private void getCodeToFormat(int startPos, int endPos, StringBuilder sb, int[] posMapping, boolean isMarkdown) { int position = 0; // original source position (minus startPos) // skip excessive line break at the beginning @@ -1439,6 +1686,9 @@ private void getCodeToFormat(int startPos, int endPos, StringBuilder sb, int[] p } else if (!ScannerHelper.isWhitespace(c)) { if (c == '*') lineStart = (this.ctm.charAt(i + 1) == ' ') ? i + 2 : i + 1; + if (c == '/' && isMarkdown) + lineStart = ((this.ctm.charAt(i + 1) == '/') && (this.ctm.charAt(i + 2) == '/')) ? i + 4 + : i + 1; break; } } @@ -1451,7 +1701,7 @@ private void getCodeToFormat(int startPos, int endPos, StringBuilder sb, int[] p } } - while (position + startPos < lineStart) { + while (position + startPos < lineStart && position < posMapping.length) { posMapping[position++] = sb.length() - 1; } @@ -1560,4 +1810,4 @@ public void finishUp() { if (this.lastFormatOffComment != null) this.tm.addDisableFormatTokenPair(this.lastFormatOffComment, this.tm.get(this.tm.size() - 1)); } -} +} \ No newline at end of file diff --git a/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/TextEditsBuilder.java b/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/TextEditsBuilder.java index 9b6fd2214c3..41ed9bde76b 100644 --- a/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/TextEditsBuilder.java +++ b/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/TextEditsBuilder.java @@ -192,8 +192,9 @@ private void bufferLineSeparator(Token token, boolean emptyLine) { return; } - boolean isTextBlock = token != null && token.tokenType == TokenNameTextBlock; - boolean isMarkdown = token != null && token.tokenType == TokenNameCOMMENT_MARKDOWN; + Token parentToken = this.parent.tm.get(this.parentTokenIndex); + boolean isTextBlock = parentToken.tokenType == TokenNameTextBlock; + boolean isMarkdown = parentToken.tokenType == TokenNameCOMMENT_MARKDOWN; this.parent.counter = this.counter; this.parent.bufferLineSeparator(null, false); if (!(isTextBlock && emptyLine && !this.options.indent_empty_lines)) diff --git a/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/TokenManager.java b/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/TokenManager.java index ab9090a9bff..329966f1235 100644 --- a/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/TokenManager.java +++ b/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/TokenManager.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2014, 2025 Mateusz Matela and others. + * Copyright (c) 2014, 2026 Mateusz Matela and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -51,7 +51,7 @@ public class TokenManager implements Iterable { private final List tokens; private final String source; - private final int tabSize; + private final int confTabSize; private final int tabChar; private final boolean wrapWithSpaces; @@ -64,7 +64,7 @@ public class TokenManager implements Iterable { public TokenManager(List tokens, String source, DefaultCodeFormatterOptions options) { this.tokens = tokens; this.source = source; - this.tabSize = options.tab_size; + this.confTabSize = options.tab_size; this.tabChar = options.tab_char; this.wrapWithSpaces = options.use_tabs_only_for_leading_indentations; this.commentWrapper = new CommentWrapExecutor(this, options); @@ -73,7 +73,7 @@ public TokenManager(List tokens, String source, DefaultCodeFormatterOptio public TokenManager(List tokens, TokenManager parent) { this.tokens = tokens; this.source = parent.source; - this.tabSize = parent.tabSize; + this.confTabSize = parent.confTabSize; this.tabChar = parent.tabChar; this.wrapWithSpaces = parent.wrapWithSpaces; this.commentWrapper = parent.commentWrapper; @@ -356,12 +356,16 @@ public int getLength(Token token, int startPosition) { * @return length, considering tabs and escaping characters as HTML entities */ public int getLength(int originalStart, int originalEnd, int startPosition) { + return getLength(originalStart, originalEnd, startPosition, this.confTabSize); + } + + public int getLength(int originalStart, int originalEnd, int startPosition, int tabSize) { int position = startPosition; for (int i = originalStart; i <= originalEnd; i++) { switch (this.source.charAt(i)) { case '\t': - if (this.tabSize > 0) - position += this.tabSize - position % this.tabSize; + if (tabSize > 0) + position += tabSize - position % tabSize; break; case '\r': case '\n': @@ -381,7 +385,7 @@ public int getLength(int originalStart, int originalEnd, int startPosition) { */ public int toIndent(int indent, boolean isWrapped) { if (this.tabChar == DefaultCodeFormatterOptions.TAB && !(isWrapped && this.wrapWithSpaces)) { - int tab = this.tabSize; + int tab = this.confTabSize; if (tab <= 0) return 0; indent = ((indent + tab - 1) / tab) * tab; @@ -465,4 +469,4 @@ public void addDisableFormatTokenPair(Token formatOffTag, Token formatOnTag) { public List getDisableFormatTokenPairs() { return this.formatOffTagPairs; } -} +} \ No newline at end of file diff --git a/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/linewrap/WrapPreparator.java b/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/linewrap/WrapPreparator.java index ca4f73c3cd7..9f215427f64 100644 --- a/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/linewrap/WrapPreparator.java +++ b/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/linewrap/WrapPreparator.java @@ -1434,16 +1434,6 @@ int getLineBreaksToPreserve(Token token1, Token token2) { || (token2 != null && !token2.isPreserveLineBreaksBefore())) { return 0; } - if (token1 != null) { - List structure = token1.getInternalStructure(); - if (structure != null && !structure.isEmpty()) - token1 = structure.get(structure.size() - 1); - } - if (token2 != null) { - List structure = token2.getInternalStructure(); - if (structure != null && !structure.isEmpty()) - token2 = structure.get(0); - } int lineBreaks = WrapPreparator.this.tm.countLineBreaksBetween(token1, token2); int toPreserve = this.options.number_of_empty_lines_to_preserve; if (token1 != null && token2 != null) @@ -1551,4 +1541,4 @@ private void handleParenthesesPositions(int openingParenIndex, int closingParenI throw new IllegalArgumentException("Unrecognized parentheses positions setting: " + positionsSetting); //$NON-NLS-1$ } } -} +} \ No newline at end of file