/*
 * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

/*
 * @test
 * @bug 7073631 7159445 7156633 8028235 8065753 8205418 8205913 8228451 8237041 8253584 8246774 8256411 8256149 8259050 8266436 8267221 8271928 8275097 8293897 8295401 8304671 8310326 8312093 8312204 8315452 8337976 8324859 8344706 8351260 8370865 8369489
 * @summary tests error and diagnostics positions
 * @author  Jan Lahoda
 * @modules jdk.compiler/com.sun.tools.javac.api
 *          jdk.compiler/com.sun.tools.javac.main
 *          jdk.compiler/com.sun.tools.javac.tree
 */

import com.sun.source.tree.BinaryTree;
import com.sun.source.tree.BlockTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.ErroneousTree;
import com.sun.source.tree.ExpressionStatementTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.IfTree;
import com.sun.source.tree.LambdaExpressionTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.ModifiersTree;
import com.sun.source.tree.PrimitiveTypeTree;
import com.sun.source.tree.StatementTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.Tree.Kind;
import com.sun.source.tree.VariableTree;
import com.sun.source.tree.WhileLoopTree;
import com.sun.source.util.JavacTask;
import com.sun.source.util.SourcePositions;
import com.sun.source.util.TreePath;
import com.sun.source.util.TreeScanner;
import com.sun.source.util.Trees;
import com.sun.tools.javac.api.JavacTaskImpl;
import com.sun.tools.javac.main.Main;
import com.sun.tools.javac.main.Main.Result;
import com.sun.tools.javac.tree.JCTree;
import java.io.IOException;
import java.io.StringWriter;
import java.io.UncheckedIOException;
import java.io.Writer;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.regex.Pattern;
import javax.lang.model.element.Modifier;
import javax.lang.model.type.TypeKind;
import javax.tools.Diagnostic;
import javax.tools.DiagnosticCollector;
import javax.tools.DiagnosticListener;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import javax.tools.ToolProvider;

import com.sun.source.tree.CaseTree;
import com.sun.source.tree.DefaultCaseLabelTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.ModuleTree;
import com.sun.source.tree.VarTypeTree;
import com.sun.source.util.TreePathScanner;
import com.sun.tools.javac.api.JavacTaskPool;
import com.sun.tools.javac.api.JavacTaskPool.Worker;
import com.sun.tools.javac.tree.JCTree.JCErroneous;
import com.sun.tools.javac.tree.Pretty;

public class JavacParserTest extends TestCase {
    static final JavaCompiler tool = ToolProvider.getSystemJavaCompiler();
    static final JavaFileManager fm = tool.getStandardFileManager(null, null, null);
    public static final String SOURCE_VERSION =
        Integer.toString(Runtime.version().feature());

    private JavacParserTest(){}

    public static void main(String... args) throws Exception {
        try (fm) {
            new JavacParserTest().run(args);
        }
    }

    class MyFileObject extends SimpleJavaFileObject {

        private String text;

        public MyFileObject(String text) {
            this("Test", text);
        }

        public MyFileObject(String fileName, String text) {
            super(URI.create("myfo:/" + fileName + ".java"), JavaFileObject.Kind.SOURCE);
            this.text = text;
        }

        @Override
        public CharSequence getCharContent(boolean ignoreEncodingErrors) {
            return text;
        }
    }
    /*
     * converts Windows to Unix style LFs for comparing strings
     */
    String normalize(String in) {
        return in.replace(System.getProperty("line.separator"), "\n");
    }

    CompilationUnitTree getCompilationUnitTree(String code) throws IOException {

        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, null, null,
                null, Arrays.asList(new MyFileObject(code)));
        CompilationUnitTree cut = ct.parse().iterator().next();
        return cut;
    }

    List<String> getErroneousTreeValues(ErroneousTree node) {

        List<String> values = new ArrayList<>();
        if (node.getErrorTrees() != null) {
            for (Tree t : node.getErrorTrees()) {
                values.add(t.toString());
            }
        } else {
            throw new RuntimeException("ERROR: No Erroneous tree "
                    + "has been created.");
        }
        return values;
    }

    @Test
    void testPositionForSuperConstructorCalls() throws IOException {
        assert tool != null;

        String code = "package test; public class Test {public Test() {super();}}";

        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, null, null,
                null, Arrays.asList(new MyFileObject(code)));
        CompilationUnitTree cut = ct.parse().iterator().next();
        SourcePositions pos = Trees.instance(ct).getSourcePositions();

        MethodTree method =
                (MethodTree) ((ClassTree) cut.getTypeDecls().get(0)).getMembers().get(0);
        ExpressionStatementTree es =
                (ExpressionStatementTree) method.getBody().getStatements().get(0);

        final int esStartPos = code.indexOf(es.toString());
        final int esEndPos = esStartPos + es.toString().length();
        assertEquals("testPositionForSuperConstructorCalls",
                esStartPos, pos.getStartPosition(cut, es));
        assertEquals("testPositionForSuperConstructorCalls",
                esEndPos, pos.getEndPosition(cut, es));

        MethodInvocationTree mit = (MethodInvocationTree) es.getExpression();

        final int mitStartPos = code.indexOf(mit.toString());
        final int mitEndPos = mitStartPos + mit.toString().length();
        assertEquals("testPositionForSuperConstructorCalls",
                mitStartPos, pos.getStartPosition(cut, mit));
        assertEquals("testPositionForSuperConstructorCalls",
                mitEndPos, pos.getEndPosition(cut, mit));

        final int methodStartPos = mitStartPos;
        final int methodEndPos = methodStartPos + mit.getMethodSelect().toString().length();
        assertEquals("testPositionForSuperConstructorCalls",
                methodStartPos, pos.getStartPosition(cut, mit.getMethodSelect()));
        assertEquals("testPositionForSuperConstructorCalls",
                methodEndPos, pos.getEndPosition(cut, mit.getMethodSelect()));
    }

    @Test
    void testPositionForEnumModifiers() throws IOException {
        final String theString = "public";
        String code = "package test; " + theString + " enum Test {A;}";

        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, null, null,
                null, Arrays.asList(new MyFileObject(code)));
        CompilationUnitTree cut = ct.parse().iterator().next();
        SourcePositions pos = Trees.instance(ct).getSourcePositions();

        ClassTree clazz = (ClassTree) cut.getTypeDecls().get(0);
        ModifiersTree mt = clazz.getModifiers();
        int spos = code.indexOf(theString);
        int epos = spos + theString.length();
        assertEquals("testPositionForEnumModifiers",
                spos, pos.getStartPosition(cut, mt));
        assertEquals("testPositionForEnumModifiers",
                epos, pos.getEndPosition(cut, mt));
    }

    @Test
    void testNewClassWithEnclosing() throws IOException {

        final String theString = "Test.this.new d()";
        String code = "package test; class Test { " +
                "class d {} private void method() { " +
                "Object o = " + theString + "; } }";

        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, null, null,
                null, Arrays.asList(new MyFileObject(code)));
        CompilationUnitTree cut = ct.parse().iterator().next();
        SourcePositions pos = Trees.instance(ct).getSourcePositions();

        ClassTree clazz = (ClassTree) cut.getTypeDecls().get(0);
        ExpressionTree est =
                ((VariableTree) ((MethodTree) clazz.getMembers().get(1)).getBody().getStatements().get(0)).getInitializer();

        final int spos = code.indexOf(theString);
        final int epos = spos + theString.length();
        assertEquals("testNewClassWithEnclosing",
                spos, pos.getStartPosition(cut, est));
        assertEquals("testNewClassWithEnclosing",
                epos, pos.getEndPosition(cut, est));
    }

    @Test
    void testPreferredPositionForBinaryOp() throws IOException {

        String code = "package test; public class Test {"
                + "private void test() {"
                + "Object o = null; boolean b = o != null && o instanceof String;"
                + "} private Test() {}}";

        CompilationUnitTree cut = getCompilationUnitTree(code);
        ClassTree clazz = (ClassTree) cut.getTypeDecls().get(0);
        MethodTree method = (MethodTree) clazz.getMembers().get(0);
        VariableTree condSt = (VariableTree) method.getBody().getStatements().get(1);
        BinaryTree cond = (BinaryTree) condSt.getInitializer();

        JCTree condJC = (JCTree) cond;
        int condStartPos = code.indexOf("&&");
        assertEquals("testPreferredPositionForBinaryOp",
                condStartPos, condJC.pos);
    }

    @Test
    void testErrorRecoveryForEnhancedForLoop142381() throws IOException {

        String code = "package test; class Test { " +
                "private void method() { " +
                "java.util.Set<String> s = null; for (a : s) {} } }";

        final List<Diagnostic<? extends JavaFileObject>> errors =
                new LinkedList<Diagnostic<? extends JavaFileObject>>();

        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm,
                new DiagnosticListener<JavaFileObject>() {
            public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
                errors.add(diagnostic);
            }
        }, null, null, Arrays.asList(new MyFileObject(code)));

        CompilationUnitTree cut = ct.parse().iterator().next();

        ClassTree clazz = (ClassTree) cut.getTypeDecls().get(0);
        StatementTree forStatement =
                ((MethodTree) clazz.getMembers().get(0)).getBody().getStatements().get(1);

        assertEquals("testErrorRecoveryForEnhancedForLoop142381",
                Kind.ENHANCED_FOR_LOOP, forStatement.getKind());
        assertFalse("testErrorRecoveryForEnhancedForLoop142381", errors.isEmpty());
    }

    @Test
    void testPositionAnnotationNoPackage187551() throws IOException {

        String code = "\n@interface Test {}";

        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, null, null,
                null, Arrays.asList(new MyFileObject(code)));

        CompilationUnitTree cut = ct.parse().iterator().next();
        ClassTree clazz = (ClassTree) cut.getTypeDecls().get(0);
        Trees t = Trees.instance(ct);

        assertEquals("testPositionAnnotationNoPackage187551",
                1, t.getSourcePositions().getStartPosition(cut, clazz));
    }

    @Test
    void testPositionMissingStatement() throws IOException {
        String code = "class C { void t() { if (true) } }";
        DiagnosticCollector<JavaFileObject> dc = new DiagnosticCollector<>();

        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, dc, null,
                null, Arrays.asList(new MyFileObject(code)));

        CompilationUnitTree cut = ct.parse().iterator().next();
        Trees trees = Trees.instance(ct);
        SourcePositions positions = trees.getSourcePositions();

        new TreeScanner<Void, Void>() {
            @Override
            public Void visitIf(IfTree it, Void v) {
                StatementTree st = it.getThenStatement();
                int startpos = (int) positions.getStartPosition(cut, st);
                int endpos = (int) positions.getEndPosition(cut, st);
                assertEquals("testPositionMissingStatement.execpos", startpos, endpos);
                assertEquals("testPositionMissingStatement.execkind",
                             Kind.EXPRESSION_STATEMENT,
                             st.getKind());
                Tree err = ((ExpressionStatementTree) st).getExpression();
                startpos = (int) positions.getStartPosition(cut, err);
                endpos = (int) positions.getEndPosition(cut, err);
                assertEquals("testPositionMissingStatement.errpos", startpos, endpos);
                assertEquals("testPositionMissingStatement.errkind",
                             Kind.ERRONEOUS,
                             err.getKind());
                return super.visitIf(it, v);
            }
        }.scan(cut, null);

        assertEquals("testPositionMissingStatement.diags", 1, dc.getDiagnostics().size());
        Diagnostic<? extends JavaFileObject> d = dc.getDiagnostics().get(0);
        int startpos = (int) d.getStartPosition();
        int pos = (int) d.getPosition();
        int endpos = (int) d.getEndPosition();
        assertEquals("testPositionMissingStatement.diagspan", startpos, endpos);
        assertEquals("testPositionMissingStatement.diagpref", startpos, pos);
    }

    @Test
    void testPositionsSane1() throws IOException {
        performPositionsSanityTest("package test; class Test { " +
                "private void method() { " +
                "java.util.List<? extends java.util.List<? extends String>> l; " +
                "} }");
    }

    @Test
    void testPositionsSane2() throws IOException {
        performPositionsSanityTest("package test; class Test { " +
                "private void method() { " +
                "java.util.List<? super java.util.List<? super String>> l; " +
                "} }");
    }

    @Test
    void testPositionsSane3() throws IOException {
        performPositionsSanityTest("package test; class Test { " +
                "private void method() { " +
                "java.util.List<? super java.util.List<?>> l; } }");
    }

    private void performPositionsSanityTest(String code) throws IOException {

        final List<Diagnostic<? extends JavaFileObject>> errors =
                new LinkedList<Diagnostic<? extends JavaFileObject>>();

        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm,
                new DiagnosticListener<JavaFileObject>() {

            public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
                errors.add(diagnostic);
            }
        }, null, null, Arrays.asList(new MyFileObject(code)));

        final CompilationUnitTree cut = ct.parse().iterator().next();
        final Trees trees = Trees.instance(ct);

        new TreeScanner<Void, Void>() {

            private long parentStart = 0;
            private long parentEnd = Integer.MAX_VALUE;

            @Override
            public Void scan(Tree node, Void p) {
                if (node == null) {
                    return null;
                }

                long start = trees.getSourcePositions().getStartPosition(cut, node);

                if (start == (-1)) {
                    return null; // synthetic tree
                }
                assertTrue(node.toString() + ":" + start + "/" + parentStart,
                        parentStart <= start);

                long prevParentStart = parentStart;

                parentStart = start;

                long end = trees.getSourcePositions().getEndPosition(cut, node);

                assertTrue(node.toString() + ":" + end + "/" + parentEnd,
                        end <= parentEnd);

                long prevParentEnd = parentEnd;

                parentEnd = end;

                super.scan(node, p);

                parentStart = prevParentStart;
                parentEnd = prevParentEnd;

                return null;
            }

            private void assertTrue(String message, boolean b) {
                if (!b) fail(message);
            }
        }.scan(cut, null);
    }

    @Test
    void testCorrectWildcardPositions1() throws IOException {
        performWildcardPositionsTest("package test; import java.util.List; " +
                "class Test { private void method() { List<? extends List<? extends String>> l; } }",

                Arrays.asList("List<? extends List<? extends String>> l;",
                "List<? extends List<? extends String>>",
                "List",
                "? extends List<? extends String>",
                "List<? extends String>",
                "List",
                "? extends String",
                "String"));
    }

    @Test
    void testCorrectWildcardPositions2() throws IOException {
        performWildcardPositionsTest("package test; import java.util.List; "
                + "class Test { private void method() { List<? super List<? super String>> l; } }",
                Arrays.asList("List<? super List<? super String>> l;",
                "List<? super List<? super String>>",
                "List",
                "? super List<? super String>",
                "List<? super String>",
                "List",
                "? super String",
                "String"));
    }

    @Test
    void testCorrectWildcardPositions3() throws IOException {
        performWildcardPositionsTest("package test; import java.util.List; " +
                "class Test { private void method() { List<? super List<?>> l; } }",

                Arrays.asList("List<? super List<?>> l;",
                "List<? super List<?>>",
                "List",
                "? super List<?>",
                "List<?>",
                "List",
                "?"));
    }

    @Test
    void testCorrectWildcardPositions4() throws IOException {
        performWildcardPositionsTest("package test; import java.util.List; " +
                "class Test { private void method() { " +
                "List<? extends List<? extends List<? extends String>>> l; } }",

                Arrays.asList("List<? extends List<? extends List<? extends String>>> l;",
                "List<? extends List<? extends List<? extends String>>>",
                "List",
                "? extends List<? extends List<? extends String>>",
                "List<? extends List<? extends String>>",
                "List",
                "? extends List<? extends String>",
                "List<? extends String>",
                "List",
                "? extends String",
                "String"));
    }

    @Test
    void testCorrectWildcardPositions5() throws IOException {
        performWildcardPositionsTest("package test; import java.util.List; " +
                "class Test { private void method() { " +
                "List<? extends List<? extends List<? extends String   >>> l; } }",
                Arrays.asList("List<? extends List<? extends List<? extends String   >>> l;",
                "List<? extends List<? extends List<? extends String   >>>",
                "List",
                "? extends List<? extends List<? extends String   >>",
                "List<? extends List<? extends String   >>",
                "List",
                "? extends List<? extends String   >",
                "List<? extends String   >",
                "List",
                "? extends String",
                "String"));
    }

    void performWildcardPositionsTest(final String code,
            List<String> golden) throws IOException {

        final List<Diagnostic<? extends JavaFileObject>> errors =
                new LinkedList<Diagnostic<? extends JavaFileObject>>();

        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm,
                new DiagnosticListener<JavaFileObject>() {
                    public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
                        errors.add(diagnostic);
                    }
                }, null, null, Arrays.asList(new MyFileObject(code)));

        final CompilationUnitTree cut = ct.parse().iterator().next();
        final List<String> content = new LinkedList<String>();
        final Trees trees = Trees.instance(ct);

        new TreeScanner<Void, Void>() {
            @Override
            public Void scan(Tree node, Void p) {
                if (node == null) {
                    return null;
                }
                long start = trees.getSourcePositions().getStartPosition(cut, node);

                if (start == (-1)) {
                    return null; // synthetic tree
                }
                long end = trees.getSourcePositions().getEndPosition(cut, node);
                String s = code.substring((int) start, (int) end);
                content.add(s);

                return super.scan(node, p);
            }
        }.scan(((MethodTree) ((ClassTree) cut.getTypeDecls().get(0)).getMembers().get(0)).getBody().getStatements().get(0), null);

        assertEquals("performWildcardPositionsTest",golden.toString(),
                content.toString());
    }

    @Test
    void testStartPositionForMethodWithoutModifiers() throws IOException {

        String code = "package t; class Test { <T> void t() {} }";

        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, null, null,
                null, Arrays.asList(new MyFileObject(code)));
        CompilationUnitTree cut = ct.parse().iterator().next();
        ClassTree clazz = (ClassTree) cut.getTypeDecls().get(0);
        MethodTree mt = (MethodTree) clazz.getMembers().get(0);
        Trees t = Trees.instance(ct);
        int start = (int) t.getSourcePositions().getStartPosition(cut, mt);
        int end = (int) t.getSourcePositions().getEndPosition(cut, mt);

        assertEquals("testStartPositionForMethodWithoutModifiers",
                "<T> void t() {}", code.substring(start, end));
    }

    @Test
    void testVariableInIfThen1() throws IOException {

        String code = "package t; class Test { " +
                "private static void t(String name) { " +
                "if (name != null) String nn = name.trim(); } }";

        DiagnosticCollector<JavaFileObject> coll =
                new DiagnosticCollector<JavaFileObject>();

        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, coll, null,
                null, Arrays.asList(new MyFileObject(code)));

        ct.parse();

        List<String> codes = new LinkedList<String>();

        for (Diagnostic<? extends JavaFileObject> d : coll.getDiagnostics()) {
            codes.add(d.getCode());
        }

        assertEquals("testVariableInIfThen1",
                Arrays.<String>asList("compiler.err.variable.not.allowed"),
                codes);
    }

    @Test
   void testVariableInIfThen2() throws IOException {

        String code = "package t; class Test { " +
                "private static void t(String name) { " +
                "if (name != null) class X {} } }";
        DiagnosticCollector<JavaFileObject> coll =
                new DiagnosticCollector<JavaFileObject>();
        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, coll, null,
                null, Arrays.asList(new MyFileObject(code)));

        ct.parse();

        List<String> codes = new LinkedList<String>();

        for (Diagnostic<? extends JavaFileObject> d : coll.getDiagnostics()) {
            codes.add(d.getCode());
        }

        assertEquals("testVariableInIfThen2",
                Arrays.<String>asList("compiler.err.class.not.allowed"), codes);
    }

    @Test
    void testVariableInIfThen3() throws IOException {

        String code = "package t; class Test { "+
                "private static void t() { " +
                "if (true) abstract class F {} }}";
        DiagnosticCollector<JavaFileObject> coll =
                new DiagnosticCollector<JavaFileObject>();
        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, coll, null,
                null, Arrays.asList(new MyFileObject(code)));

        ct.parse();

        List<String> codes = new LinkedList<String>();

        for (Diagnostic<? extends JavaFileObject> d : coll.getDiagnostics()) {
            codes.add(d.getCode());
        }

        assertEquals("testVariableInIfThen3",
                Arrays.<String>asList("compiler.err.class.not.allowed"), codes);
    }

    @Test
    void testVariableInIfThen4() throws IOException {

        String code = "package t; class Test { "+
                "private static void t(String name) { " +
                "if (name != null) interface X {} } }";
        DiagnosticCollector<JavaFileObject> coll =
                new DiagnosticCollector<JavaFileObject>();
        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, coll, null,
                null, Arrays.asList(new MyFileObject(code)));

        ct.parse();

        List<String> codes = new LinkedList<String>();

        for (Diagnostic<? extends JavaFileObject> d : coll.getDiagnostics()) {
            codes.add(d.getCode());
        }

        assertEquals("testVariableInIfThen4",
                Arrays.<String>asList("compiler.err.class.not.allowed"), codes);
    }

    @Test
    void testVariableInIfThen5() throws IOException {

        String code = "package t; class Test { "+
                "private static void t() { " +
                "if (true) } }";
        DiagnosticCollector<JavaFileObject> coll =
                new DiagnosticCollector<JavaFileObject>();
        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, coll, null,
                null, Arrays.asList(new MyFileObject(code)));

        ct.parse();

        List<String> codes = new LinkedList<String>();

        for (Diagnostic<? extends JavaFileObject> d : coll.getDiagnostics()) {
            codes.add(d.getCode());
        }

        assertEquals("testVariableInIfThen5",
                Arrays.<String>asList("compiler.err.illegal.start.of.stmt"),
                codes);
    }

    // see javac bug #6882235, NB bug #98234:
    @Test
    void testMissingExponent() throws IOException {

        String code = "\nclass Test { { System.err.println(0e); } }";

        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, null, null,
                null, Arrays.asList(new MyFileObject(code)));

        assertNotNull(ct.parse().iterator().next());
    }

    @Test
    void testTryResourcePos() throws IOException {

        final String code = "package t; class Test { " +
                "{ try (java.io.InputStream in = null) { } } }";

        CompilationUnitTree cut = getCompilationUnitTree(code);

        new TreeScanner<Void, Void>() {
            @Override
            public Void visitVariable(VariableTree node, Void p) {
                if ("in".contentEquals(node.getName())) {
                    JCTree.JCVariableDecl var = (JCTree.JCVariableDecl) node;
                    assertEquals("testTryResourcePos", "in = null) { } } }",
                            code.substring(var.pos));
                }
                return super.visitVariable(node, p);
            }
        }.scan(cut, null);
    }

    @Test
    void testVarPos() throws IOException {

        final String code = "package t; class Test { " +
                "{ java.io.InputStream in = null; } }";

        CompilationUnitTree cut = getCompilationUnitTree(code);

        new TreeScanner<Void, Void>() {

            @Override
            public Void visitVariable(VariableTree node, Void p) {
                if ("in".contentEquals(node.getName())) {
                    JCTree.JCVariableDecl var = (JCTree.JCVariableDecl) node;
                    assertEquals("testVarPos","in = null; } }",
                            code.substring(var.pos));
                }
                return super.visitVariable(node, p);
            }
        }.scan(cut, null);
    }

    // expected erroneous tree: int x = y;(ERROR);
    @Test
    void testOperatorMissingError() throws IOException {

        String code = "package test; public class ErrorTest { "
                + "void method() { int x = y  z } }";
        CompilationUnitTree cut = getCompilationUnitTree(code);
        final List<String> values = new ArrayList<>();
        final List<String> expectedValues =
                new ArrayList<>(Arrays.asList("[z]"));

        new TreeScanner<Void, Void>() {
            @Override
            public Void visitErroneous(ErroneousTree node, Void p) {
                values.add(getErroneousTreeValues(node).toString());
                return null;

            }
        }.scan(cut, null);

        assertEquals("testOperatorMissingError: The Erroneous tree "
                + "error values: " + values
                + " do not match expected error values: "
                + expectedValues, values, expectedValues);
    }

    // expected erroneous tree:  String s = (ERROR);
    @Test
    void testMissingParenthesisError() throws IOException {

        String code = "package test; public class ErrorTest { "
                + "void f() {String s = new String; } }";
        CompilationUnitTree cut = getCompilationUnitTree(code);
        final List<String> values = new ArrayList<>();
        final List<String> expectedValues =
                new ArrayList<>(Arrays.asList("[new String()]"));

        new TreeScanner<Void, Void>() {
            @Override
            public Void visitErroneous(ErroneousTree node, Void p) {
                values.add(getErroneousTreeValues(node).toString());
                return null;
            }
        }.scan(cut, null);

        assertEquals("testMissingParenthesisError: The Erroneous tree "
                + "error values: " + values
                + " do not match expected error values: "
                + expectedValues, values, expectedValues);
    }

    // expected erroneous tree: package test; (ERROR)(ERROR)
    @Test
    void testMissingClassError() throws IOException {

        String code = "package Test; clas ErrorTest {  "
                + "void f() {String s = new String(); } }";
        CompilationUnitTree cut = getCompilationUnitTree(code);
        final List<String> values = new ArrayList<>();
        final List<String> expectedValues =
                new ArrayList<>(Arrays.asList("[, clas]", "[]"));

        new TreeScanner<Void, Void>() {
            @Override
            public Void visitErroneous(ErroneousTree node, Void p) {
                values.add(getErroneousTreeValues(node).toString());
                return null;
            }
        }.scan(cut, null);

        assertEquals("testMissingClassError: The Erroneous tree "
                + "error values: " + values
                + " do not match expected error values: "
                + expectedValues, values, expectedValues);
    }

    // expected erroneous tree: void m1(int i) {(ERROR);{(ERROR);}
    @Test
    void testSwitchError() throws IOException {

        String code = "package test; public class ErrorTest { "
                + "int numDays; void m1(int i) { switchh {i} { case 1: "
                + "numDays = 31; break; } } }";
        CompilationUnitTree cut = getCompilationUnitTree(code);
        final List<String> values = new ArrayList<>();
        final List<String> expectedValues =
                new ArrayList<>(Arrays.asList("[switchh]", "[i]"));

        new TreeScanner<Void, Void>() {
            @Override
            public Void visitErroneous(ErroneousTree node, Void p) {
                values.add(getErroneousTreeValues(node).toString());
                return null;
            }
        }.scan(cut, null);

        assertEquals("testSwitchError: The Erroneous tree "
                + "error values: " + values
                + " do not match expected error values: "
                + expectedValues, values, expectedValues);
    }

    // expected erroneous tree: class ErrorTest {(ERROR)
    @Test
    void testMethodError() throws IOException {

        String code = "package Test; class ErrorTest {  "
                + "static final void f) {String s = new String(); } }";
        CompilationUnitTree cut = cut = getCompilationUnitTree(code);

        final List<String> values = new ArrayList<>();
        final List<String> expectedValues =
                new ArrayList<>(Arrays.asList("[\nstatic final void f();]"));

        new TreeScanner<Void, Void>() {
            @Override
            public Void visitErroneous(ErroneousTree node, Void p) {
                values.add(normalize(getErroneousTreeValues(node).toString()));
                return null;
            }
        }.scan(cut, null);

        assertEquals("testMethodError: The Erroneous tree "
                + "error value: " + values
                + " does not match expected error values: "
                + expectedValues, values, expectedValues);
    }

    @Test
    void testPositionBrokenSource126732a() throws IOException {
        String[] commands = new String[]{
            "return Runnable()",
            "do { } while (true)",
            "throw UnsupportedOperationException()",
            "assert true",
            "1 + 1",};

        for (String command : commands) {

            String code = "package test;\n"
                    + "public class Test {\n"
                    + "    public static void test() {\n"
                    + "        " + command + " {\n"
                    + "                new Runnable() {\n"
                    + "        };\n"
                    + "    }\n"
                    + "}";
            JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, null,
                    null, null, Arrays.asList(new MyFileObject(code)));
            CompilationUnitTree cut = ct.parse().iterator().next();

            ClassTree clazz = (ClassTree) cut.getTypeDecls().get(0);
            MethodTree method = (MethodTree) clazz.getMembers().get(0);
            List<? extends StatementTree> statements =
                    method.getBody().getStatements();

            StatementTree ret = statements.get(0);
            StatementTree block = statements.get(1);

            Trees t = Trees.instance(ct);
            int len = code.indexOf(command + " {") + (command + " ").length();
            assertEquals(command, len,
                    t.getSourcePositions().getEndPosition(cut, ret));
            assertEquals(command, len,
                    t.getSourcePositions().getStartPosition(cut, block));
        }
    }

    @Test
    void testPositionBrokenSource126732b() throws IOException {
        String[] commands = new String[]{
            "break",
            "break A",
            "continue ",
            "continue A",};

        for (String command : commands) {

            String code = "package test;\n"
                    + "public class Test {\n"
                    + "    public static void test() {\n"
                    + "        while (true) {\n"
                    + "            " + command + " {\n"
                    + "                new Runnable() {\n"
                    + "        };\n"
                    + "        }\n"
                    + "    }\n"
                    + "}";

            JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, null,
                    null, null, Arrays.asList(new MyFileObject(code)));
            CompilationUnitTree cut = ct.parse().iterator().next();

            ClassTree clazz = (ClassTree) cut.getTypeDecls().get(0);
            MethodTree method = (MethodTree) clazz.getMembers().get(0);
            List<? extends StatementTree> statements =
                    ((BlockTree) ((WhileLoopTree) method.getBody().getStatements().get(0)).getStatement()).getStatements();

            StatementTree ret = statements.get(0);
            StatementTree block = statements.get(1);

            Trees t = Trees.instance(ct);
            int len = code.indexOf(command + " {") + (command + " ").length();
            assertEquals(command, len,
                    t.getSourcePositions().getEndPosition(cut, ret));
            assertEquals(command, len,
                    t.getSourcePositions().getStartPosition(cut, block));
        }
    }

    @Test
    void testStartPositionEnumConstantInit() throws IOException {

        String code = "package t; enum Test { AAA; }";

        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, null, null,
                null, Arrays.asList(new MyFileObject(code)));
        CompilationUnitTree cut = ct.parse().iterator().next();
        ClassTree clazz = (ClassTree) cut.getTypeDecls().get(0);
        VariableTree enumAAA = (VariableTree) clazz.getMembers().get(0);
        Trees t = Trees.instance(ct);
        int start = (int) t.getSourcePositions().getStartPosition(cut,
                enumAAA.getInitializer());

        assertEquals("testStartPositionEnumConstantInit", 23, start);
    }

    @Test
    void testVoidLambdaParameter() throws IOException {
        String code = "package t; class Test { " +
                "Runnable r = (void v) -> { };" +
                "}";
        DiagnosticCollector<JavaFileObject> coll =
                new DiagnosticCollector<>();
        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, coll, null,
                null, Arrays.asList(new MyFileObject(code)));

        CompilationUnitTree cut = ct.parse().iterator().next();
        ClassTree clazz = (ClassTree) cut.getTypeDecls().get(0);
        VariableTree field = (VariableTree) clazz.getMembers().get(0);

        assertEquals("actual kind: " + field.getInitializer().getKind(),
                     field.getInitializer().getKind(),
                     Kind.LAMBDA_EXPRESSION);

        LambdaExpressionTree lambda = (LambdaExpressionTree) field.getInitializer();

        assertEquals("actual parameters: " + lambda.getParameters().size(),
                     lambda.getParameters().size(),
                     1);

        Tree paramType = lambda.getParameters().get(0).getType();

        assertEquals("actual parameter type: " + paramType.getKind(),
                     paramType.getKind(),
                     Kind.PRIMITIVE_TYPE);

        TypeKind primitiveTypeKind = ((PrimitiveTypeTree) paramType).getPrimitiveTypeKind();

        assertEquals("actual parameter type: " + primitiveTypeKind,
                     primitiveTypeKind,
                     TypeKind.VOID);
    }

    @Test //JDK-8065753
    void testWrongFirstToken() throws IOException {
        String code = "<";
        String expectedErrors = "Test.java:1:1: compiler.err.class.method.or.field.expected\n" +
                                "1 error\n";
        StringWriter out = new StringWriter();
        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(out, fm, null,
                Arrays.asList("-XDrawDiagnostics"), null, Arrays.asList(new MyFileObject(code)));

        Result errorCode = ct.doCall();
        assertEquals("the error code is not correct; actual:" + errorCode, Main.Result.ERROR, errorCode);
        String actualErrors = normalize(out.toString());
        assertEquals("the error message is not correct, actual: " + actualErrors, expectedErrors, actualErrors);
    }

    @Test //JDK-8205913
    void testForInit() throws IOException {
        String code = "class T { void t() { for (n : ns) { } } }";
        String expectedErrors = "Test.java:1:27: compiler.err.bad.initializer: for-loop\n";
        StringWriter out = new StringWriter();
        JavacTask ct = (JavacTask) tool.getTask(out, fm, null,
                Arrays.asList("-XDrawDiagnostics"), null, Arrays.asList(new MyFileObject(code)));

        Iterable<? extends CompilationUnitTree> cuts = ct.parse();
        boolean[] foundVar = new boolean[1];

        new TreePathScanner<Void, Void>() {
            @Override public Void visitVariable(VariableTree vt, Void p) {
                assertNotNull(vt.getModifiers());
                assertNotNull(vt.getType());
                assertNotNull(vt.getName());
                assertEquals("name should be <error>", "<error>", vt.getName().toString());
                foundVar[0] = true;
                return super.visitVariable(vt, p);
            }
        }.scan(cuts, null);

        if (!foundVar[0]) {
            fail("haven't found a variable");
        }

        String actualErrors = normalize(out.toString());
        assertEquals("the error message is not correct, actual: " + actualErrors, expectedErrors, actualErrors);
    }

    @Test //JDK-821742
    void testCompDeclVarType() throws IOException {
        String code = "package test; public class Test {"
                + "private void test() {"
                + "var v1 = 10,v2 = 12;"
                + "} private Test() {}}";

        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, null,
                null, null, Arrays.asList(new MyFileObject(code)));
        CompilationUnitTree cut = ct.parse().iterator().next();
        ct.enter();
        ct.analyze();
        ClassTree clazz = (ClassTree) cut.getTypeDecls().get(0);
        MethodTree method = (MethodTree) clazz.getMembers().get(0);
        VariableTree stmt1 = (VariableTree) method.getBody().getStatements().get(0);
        VariableTree stmt2 = (VariableTree) method.getBody().getStatements().get(1);
        Tree v1Type = stmt1.getType();
        Tree v2Type = stmt2.getType();
        assertEquals("Implicit type for v1 is not correct: ", Kind.VAR_TYPE, v1Type.getKind());
        assertEquals("Implicit type for v2 is not correct: ", Kind.VAR_TYPE, v2Type.getKind());
    }

    @Test
    void testCaseBodyStatements() throws IOException {
        String code = "class C {" +
                      "    void t(int i) {" +
                      "        switch (i) {" +
                      "            case 0 -> i++;" +
                      "            case 1 -> { i++; }" +
                      "            case 2 -> throw new RuntimeException();" +
                      "            case 3 -> if (true) ;" +
                      "            default -> i++;" +
                      "        }" +
                      "        switch (i) {" +
                      "            case 0: i++; break;" +
                      "            case 1: { i++; break;}" +
                      "            case 2: throw new RuntimeException();" +
                      "            case 3: if (true) ; break;" +
                      "            default: i++; break;" +
                      "        }" +
                      "        int j = switch (i) {" +
                      "            case 0 -> i + 1;" +
                      "            case 1 -> { yield i + 1; }" +
                      "            default -> throw new RuntimeException();" +
                      "        };" +
                      "        int k = switch (i) {" +
                      "            case 0: yield i + 1;" +
                      "            case 1: { yield i + 1; }" +
                      "            default: throw new RuntimeException();" +
                      "        };" +
                      "    }" +
                      "}";
        String expectedErrors = "Test.java:1:178: compiler.err.switch.case.unexpected.statement\n";
        StringWriter out = new StringWriter();
        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(out, fm, null,
                Arrays.asList("-XDrawDiagnostics"),
                null, Arrays.asList(new MyFileObject(code)));

        CompilationUnitTree cut = ct.parse().iterator().next();
        Trees trees = Trees.instance(ct);
        List<String> spans = new ArrayList<>();

        new TreePathScanner<Void, Void>() {
            @Override
            public Void visitCase(CaseTree tree, Void v) {
                if (tree.getBody() != null) {
                    int start = (int) trees.getSourcePositions().getStartPosition(cut, tree.getBody());
                    int end = (int) trees.getSourcePositions().getEndPosition(cut, tree.getBody());
                    spans.add(code.substring(start, end));
                } else {
                    spans.add("<null>");
                }
                return super.visitCase(tree, v);
            }
        }.scan(cut, null);

        List<String> expectedSpans = List.of(
                "i++;", "{ i++; }", "throw new RuntimeException();", "if (true) ;", "i++;",
                "<null>", "<null>", "<null>", "<null>", "<null>",
                "i + 1"/*TODO semicolon?*/, "{ yield i + 1; }", "throw new RuntimeException();",
                "<null>", "<null>", "<null>");
        assertEquals("the error spans are not correct; actual:" + spans, expectedSpans, spans);
        String toString = normalize(cut.toString());
        String expectedToString =
                "\n" +
                "class C {\n" +
                "    \n" +
                "    void t(int i) {\n" +
                "        switch (i) {\n" +
                "        case 0 -> i++;\n" +
                "        case 1 -> {\n" +
                "            i++;\n" +
                "        }\n" +
                "        case 2 -> throw new RuntimeException();\n" +
                "        case 3 -> if (true) ;\n" +
                "        default -> i++;\n" +
                "        }\n" +
                "        switch (i) {\n" +
                "        case 0:\n" +
                "            i++;\n" +
                "            break;\n" +
                "        \n" +
                "        case 1:\n" +
                "            {\n" +
                "                i++;\n" +
                "                break;\n" +
                "            }\n" +
                "        \n" +
                "        case 2:\n" +
                "            throw new RuntimeException();\n" +
                "        \n" +
                "        case 3:\n" +
                "            if (true) ;\n" +
                "            break;\n" +
                "        \n" +
                "        default:\n" +
                "            i++;\n" +
                "            break;\n" +
                "        \n" +
                "        }\n" +
                "        int j = switch (i) {\n" +
                "        case 0 -> yield i + 1;\n" +
                "        case 1 -> {\n" +
                "            yield i + 1;\n" +
                "        }\n" +
                "        default -> throw new RuntimeException();\n" +
                "        };\n" +
                "        int k = switch (i) {\n" +
                "        case 0:\n" +
                "            yield i + 1;\n" +
                "        \n" +
                "        case 1:\n" +
                "            {\n" +
                "                yield i + 1;\n" +
                "            }\n" +
                "        \n" +
                "        default:\n" +
                "            throw new RuntimeException();\n" +
                "        \n" +
                "        };\n" +
                "    }\n" +
                "}";
        System.err.println("toString:");
        System.err.println(toString);
        System.err.println("expectedToString:");
        System.err.println(expectedToString);
        assertEquals("the error spans are not correct; actual:" + toString, expectedToString, toString);
        String actualErrors = normalize(out.toString());
        assertEquals("the error message is not correct, actual: " + actualErrors, expectedErrors, actualErrors);
    }

    @Test
    void testTypeParamsWithoutMethod() throws IOException {
        assert tool != null;

        String code = "package test; class Test { /**javadoc*/ |public <T> |}";
        String[] parts = code.split("\\|");

        code = parts[0] + parts[1] + parts[2];

        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, null, null,
                null, Arrays.asList(new MyFileObject(code)));
        Trees trees = Trees.instance(ct);
        SourcePositions pos = trees.getSourcePositions();
        CompilationUnitTree cut = ct.parse().iterator().next();
        ClassTree clazz = (ClassTree) cut.getTypeDecls().get(0);
        ErroneousTree err = (ErroneousTree) clazz.getMembers().get(0);
        MethodTree method = (MethodTree) err.getErrorTrees().get(0);

        final int methodStart = parts[0].length();
        final int methodEnd = parts[0].length() + parts[1].length();
        assertEquals("testTypeParamsWithoutMethod",
                methodStart, pos.getStartPosition(cut, method));
        assertEquals("testTypeParamsWithoutMethod",
                methodEnd, pos.getEndPosition(cut, method));

        TreePath path2Method = new TreePath(new TreePath(new TreePath(cut), clazz), method);
        String javadoc = trees.getDocComment(path2Method);

        if (!"javadoc".equals(javadoc)) {
            throw new AssertionError("Expected javadoc not found, actual javadoc: " + javadoc);
        }
    }

    @Test
    void testAnalyzeParensWithComma1() throws IOException {
        assert tool != null;

        String code = "package test; class Test { FI fi = |(s, |";
        String[] parts = code.split("\\|", 3);

        code = parts[0] + parts[1] + parts[2];

        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, null, null,
                null, Arrays.asList(new MyFileObject(code)));
        Trees trees = Trees.instance(ct);
        SourcePositions pos = trees.getSourcePositions();
        CompilationUnitTree cut = ct.parse().iterator().next();
        boolean[] found = new boolean[1];

        new TreeScanner<Void, Void>() {
            @Override
            public Void visitLambdaExpression(LambdaExpressionTree tree, Void v) {
                found[0] = true;
                int lambdaStart = parts[0].length();
                int lambdaEnd = parts[0].length() + parts[1].length();
                assertEquals("testAnalyzeParensWithComma1",
                        lambdaStart, pos.getStartPosition(cut, tree));
                assertEquals("testAnalyzeParensWithComma1",
                        lambdaEnd, pos.getEndPosition(cut, tree));
                return null;
            }
        }.scan(cut, null);

        assertTrue("testAnalyzeParensWithComma1", found[0]);
    }

    @Test
    void testAnalyzeParensWithComma2() throws IOException {
        assert tool != null;

        String code = "package test; class Test { FI fi = |(s, o)|";
        String[] parts = code.split("\\|", 3);

        code = parts[0] + parts[1] + parts[2];

        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, null, null,
                null, Arrays.asList(new MyFileObject(code)));
        Trees trees = Trees.instance(ct);
        SourcePositions pos = trees.getSourcePositions();
        CompilationUnitTree cut = ct.parse().iterator().next();
        boolean[] found = new boolean[1];

        new TreeScanner<Void, Void>() {
            @Override
            public Void visitLambdaExpression(LambdaExpressionTree tree, Void v) {
                found[0] = true;
                int lambdaStart = parts[0].length();
                int lambdaEnd = parts[0].length() + parts[1].length();
                assertEquals("testAnalyzeParensWithComma2",
                        lambdaStart, pos.getStartPosition(cut, tree));
                assertEquals("testAnalyzeParensWithComma2",
                        lambdaEnd, pos.getEndPosition(cut, tree));
                return null;
            }
        }.scan(cut, null);

        assertTrue("testAnalyzeParensWithComma2", found[0]);
    }

    @Test
    void testBrokenEnum1() throws IOException {
        assert tool != null;

        String code = "package test; class Test { enum E { A, B, C. D, E, F; } }";
        StringWriter output = new StringWriter();
        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(output, fm, null, List.of("-XDrawDiagnostics"),
                null, Arrays.asList(new MyFileObject(code)));
        CompilationUnitTree cut = ct.parse().iterator().next();
        List<String> actual = List.of(output.toString().split("\r?\n"));
        List<String> expected = List.of("Test.java:1:44: compiler.err.expected3: ',', '}', ';'");

        assertEquals("The expected and actual errors do not match, actual errors: " + actual,
                     actual,
                     expected);

        String actualAST = cut.toString().replaceAll("\r*\n", "\n");
        String expectedAST = "package test;\n" +
                             "\n" +
                             "class Test {\n" +
                             "    \n" +
                             "    enum E {\n" +
                             "        /*public static final*/ A /* = new E() */ /*enum*/ ,\n" +
                             "        /*public static final*/ B /* = new E() */ /*enum*/ ,\n" +
                             "        /*public static final*/ C /* = new E() */ /*enum*/ ,\n" +
                             "        /*public static final*/ D /* = new E() */ /*enum*/ ,\n" +
                             "        /*public static final*/ E /* = new E() */ /*enum*/ ,\n" +
                             "        /*public static final*/ F /* = new E() */ /*enum*/ ;\n" +
                             "        (ERROR) <error>;\n" +
                             "    }\n" +
                             "}";
        assertEquals("The expected and actual AST do not match, actual AST: " + actualAST,
                     actualAST,
                     expectedAST);
    }

    @Test
    void testBrokenEnum2() throws IOException {
        assert tool != null;

        String code = "package test; class Test { enum E { A, B, C void t() {} } }";
        StringWriter output = new StringWriter();
        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(output, fm, null, List.of("-XDrawDiagnostics"),
                null, Arrays.asList(new MyFileObject(code)));
        CompilationUnitTree cut = ct.parse().iterator().next();
        List<String> actual = List.of(output.toString().split("\r?\n"));
        List<String> expected = List.of("Test.java:1:44: compiler.err.expected3: ',', '}', ';'");

        assertEquals("The expected and actual errors do not match, actual errors: " + actual,
                     actual,
                     expected);

        String actualAST = cut.toString().replaceAll("\r*\n", "\n");
        String expectedAST = "package test;\n" +
                             "\n" +
                             "class Test {\n" +
                             "    \n" +
                             "    enum E {\n" +
                             "        /*public static final*/ A /* = new E() */ /*enum*/ ,\n" +
                             "        /*public static final*/ B /* = new E() */ /*enum*/ ,\n" +
                             "        /*public static final*/ C /* = new E() */ /*enum*/ ;\n" +
                             "        \n" +
                             "        void t() {\n" +
                             "        }\n" +
                             "    }\n" +
                             "}";
        assertEquals("The expected and actual AST do not match, actual AST: " + actualAST,
                     actualAST,
                     expectedAST);
    }

    @Test
    void testBrokenEnum3() throws IOException {
        assert tool != null;

        String code = "package test; class Test { enum E { , void t() {} } }";
        StringWriter output = new StringWriter();
        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(output, fm, null, List.of("-XDrawDiagnostics"),
                null, Arrays.asList(new MyFileObject(code)));
        CompilationUnitTree cut = ct.parse().iterator().next();
        List<String> actual = List.of(output.toString().split("\r?\n"));
        List<String> expected = List.of("Test.java:1:38: compiler.err.expected2: '}', ';'");

        assertEquals("The expected and actual errors do not match, actual errors: " + actual,
                     actual,
                     expected);

        String actualAST = cut.toString().replaceAll("\r*\n", "\n");
        String expectedAST = "package test;\n" +
                             "\n" +
                             "class Test {\n" +
                             "    \n" +
                             "    enum E {\n" +
                             ";\n" +
                             "        \n" +
                             "        void t() {\n" +
                             "        }\n" +
                             "    }\n" +
                             "}";
        assertEquals("The expected and actual AST do not match, actual AST: " + actualAST,
                     actualAST,
                     expectedAST);
    }

    @Test
    void testBrokenEnum4() throws IOException {
        assert tool != null;

        String code = "package test; class Test { enum E { A, B, C, void t() {} } }";
        StringWriter output = new StringWriter();
        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(output, fm, null, List.of("-XDrawDiagnostics"),
                null, Arrays.asList(new MyFileObject(code)));
        CompilationUnitTree cut = ct.parse().iterator().next();
        List<String> actual = List.of(output.toString().split("\r?\n"));
        List<String> expected = List.of("Test.java:1:46: compiler.err.enum.constant.expected");

        assertEquals("The expected and actual errors do not match, actual errors: " + actual,
                     actual,
                     expected);

        String actualAST = cut.toString().replaceAll("\r*\n", "\n");
        String expectedAST = "package test;\n" +
                             "\n" +
                             "class Test {\n" +
                             "    \n" +
                             "    enum E {\n" +
                             "        /*public static final*/ A /* = new E() */ /*enum*/ ,\n" +
                             "        /*public static final*/ B /* = new E() */ /*enum*/ ,\n" +
                             "        /*public static final*/ C /* = new E() */ /*enum*/ ;\n" +
                             "        \n" +
                             "        void t() {\n" +
                             "        }\n" +
                             "    }\n" +
                             "}";
        assertEquals("The expected and actual AST do not match, actual AST: " + actualAST,
                     actualAST,
                     expectedAST);
    }

    @Test
    void testBrokenEnum5() throws IOException {
        assert tool != null;

        String code = "package test; class Test { enum E { A; void t() {} B; } }";
        StringWriter output = new StringWriter();
        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(output, fm, null, List.of("-XDrawDiagnostics"),
                null, Arrays.asList(new MyFileObject(code)));
        CompilationUnitTree cut = ct.parse().iterator().next();
        List<String> actual = List.of(output.toString().split("\r?\n"));
        List<String> expected = List.of("Test.java:1:52: compiler.err.enum.constant.not.expected");

        assertEquals("The expected and actual errors do not match, actual errors: " + actual,
                     actual,
                     expected);

        String actualAST = cut.toString().replaceAll("\r*\n", "\n");
        String expectedAST = "package test;\n" +
                             "\n" +
                             "class Test {\n" +
                             "    \n" +
                             "    enum E {\n" +
                             "        /*public static final*/ A /* = new E() */ /*enum*/ ,\n" +
                             "        /*public static final*/ B /* = new E() */ /*enum*/ ;\n" +
                             "        \n" +
                             "        void t() {\n" +
                             "        }\n" +
                             "    }\n" +
                             "}";
        assertEquals("The expected and actual AST do not match, actual AST: " + actualAST,
                     actualAST,
                     expectedAST);
    }

    @Test
    void testCompoundAssignment() throws IOException {
        assert tool != null;

        String code = "package test; class Test { v += v v;}";
        StringWriter output = new StringWriter();
        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(output, fm, null, List.of("-XDrawDiagnostics"),
                null, Arrays.asList(new MyFileObject(code)));
        CompilationUnitTree cut = ct.parse().iterator().next();
        List<String> actual = List.of(output.toString().split("\r?\n"));
        List<String> expected = List.of("Test.java:1:29: compiler.err.expected: token.identifier");

        assertEquals("The expected and actual errors do not match, actual errors: " + actual,
                     actual,
                     expected);

        String actualAST = cut.toString().replaceAll("\\R", "\n");
        String expectedAST = "package test;\n" +
                             "\n" +
                             "class Test {\n" +
                             "    v <error>;\n" +
                             "    v v;\n" +
                             "}";
        assertEquals("The expected and actual AST do not match, actual AST: " + actualAST,
                     actualAST,
                     expectedAST);
    }

    @Test
    void testStartAndEndPositionForClassesInPermitsClause() throws IOException {
        String code = "package t; sealed class Test permits Sub1, Sub2 {} final class Sub1 extends Test {} final class Sub2 extends Test {}";
        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, null,
                null, null, Arrays.asList(new MyFileObject(code)));
        CompilationUnitTree cut = ct.parse().iterator().next();
        ClassTree clazz = (ClassTree) cut.getTypeDecls().get(0);
        List<? extends Tree> permitsList = clazz.getPermitsClause();
        assertEquals("testStartAndEndPositionForClassesInPermitsClause", 2, permitsList.size());
        Trees t = Trees.instance(ct);
        List<String> expected = List.of("Sub1", "Sub2");
        int i = 0;
        for (Tree permitted: permitsList) {
            int start = (int) t.getSourcePositions().getStartPosition(cut, permitted);
            int end = (int) t.getSourcePositions().getEndPosition(cut, permitted);
            assertEquals("testStartAndEndPositionForClassesInPermitsClause", expected.get(i++), code.substring(start, end));
        }
    }

    @Test //JDK-8237041
    void testDeepNestingNoClose() throws IOException {
        //verify that many nested unclosed classes do not crash javac
        //due to the safety fallback in JavacParser.reportSyntaxError:
        String code = "package t; class Test {\n";
        for (int i = 0; i < 100; i++) {
            code += "class C" + i + " {\n";
        }
        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, null, List.of("-XDdev"),
                null, Arrays.asList(new MyFileObject(code)));
        Result result = ct.doCall();
        assertEquals("Expected a (plain) error, got: " + result, result, Result.ERROR);
    }

    @Test //JDK-8237041
    void testErrorRecoveryClassNotBrace() throws IOException {
        //verify the AST form produced for classes without opening brace
        //(classes without an opening brace do not nest the upcoming content):
        String code = """
                      package t;
                      class Test {
                          String.class,
                          String.class,
                          class A
                          public
                          class B
                      }
                      """;
        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, null, List.of("-XDdev"),
                null, Arrays.asList(new MyFileObject(code)));
        String ast = ct.parse().iterator().next().toString().replaceAll("\\R", "\n");
        String expected = """
                          package t;
                          \n\
                          class Test {
                              String.<error> <error>;
                              \n\
                              class <error> {
                              }
                              \n\
                              class <error> {
                              }
                              \n\
                              class A {
                              }
                              \n\
                              public class B {
                              }
                          }""";
        assertEquals("Unexpected AST, got:\n" + ast, expected, ast);
    }

    @Test //JDK-8253584
    void testElseRecovery() throws IOException {
        //verify the errors and AST form produced for member selects which are
        //missing the selected member name:
        String code = """
                      package t;
                      class Test {
                          void t() {
                              if (true) {
                                  s().
                              } else {
                              }
                          }
                          String s() {
                              return null;
                          }
                      }
                      """;
        StringWriter out = new StringWriter();
        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(out, fm, null, List.of("-XDrawDiagnostics"),
                null, Arrays.asList(new MyFileObject(code)));
        String ast = ct.parse().iterator().next().toString().replaceAll("\\R", "\n");
        String expected = """
                          package t;
                          \n\
                          class Test {
                              \n\
                              void t() {
                                  if (true) {
                                      (ERROR);
                                  } else {
                                  }
                              }
                              \n\
                              String s() {
                                  return null;
                              }
                          } """;
        assertEquals("Unexpected AST, got:\n" + ast, expected, ast);
        assertEquals("Unexpected errors, got:\n" + out.toString(),
                     out.toString().replaceAll("\\R", "\n"),
                     """
                     Test.java:5:17: compiler.err.expected: token.identifier
                     Test.java:5:16: compiler.err.not.stmt
                     """);
    }

    @Test
    void testAtRecovery() throws IOException {
        //verify the errors and AST form produced for member selects which are
        //missing the selected member name and are followed by an annotation:
        String code = """
                      package t;
                      class Test {
                          int i1 = "".
                          @Deprecated
                          void t1() {
                          }
                          int i2 = String.
                          @Deprecated
                          void t2() {
                          }
                      }
                      """;
        StringWriter out = new StringWriter();
        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(out, fm, null, List.of("-XDrawDiagnostics"),
                null, Arrays.asList(new MyFileObject(code)));
        String ast = ct.parse().iterator().next().toString().replaceAll("\\R", "\n");
        String expected = """
                          package t;
                          \n\
                          class Test {
                              int i1 = "".<error>;
                              \n\
                              @Deprecated
                              void t1() {
                              }
                              int i2 = String.<error>;
                              \n\
                              @Deprecated
                              void t2() {
                              }
                          } """;
        assertEquals("Unexpected AST, got:\n" + ast, expected, ast);
        assertEquals("Unexpected errors, got:\n" + out.toString(),
                     out.toString().replaceAll("\\R", "\n"),
                     """
                     Test.java:3:17: compiler.err.expected: token.identifier
                     Test.java:7:21: compiler.err.expected: token.identifier
                     """);
    }

    @Test //JDK-8256411
    void testBasedAnonymous() throws IOException {
        String code = """
                      package t;
                      class Test {
                          class I {}
                          static Object I = new Test().new I() {};
                      }
                      """;
        StringWriter out = new StringWriter();
        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(out, fm, null, null,
                null, Arrays.asList(new MyFileObject(code)));
        CompilationUnitTree cut = ct.parse().iterator().next();
        Trees trees = Trees.instance(ct);
        SourcePositions sp = trees.getSourcePositions();
        ct.analyze();
        List<String> span = new ArrayList<>();
        new TreeScanner<Void, Void>() {
            public Void visitClass(ClassTree ct, Void v) {
                if (ct.getExtendsClause() != null) {
                    int start = (int) sp.getStartPosition(cut,
                                                           ct.getExtendsClause());
                    int end   = (int) sp.getEndPosition(cut,
                                                        ct.getExtendsClause());
                    span.add(code.substring(start, end));
                }
                return super.visitClass(ct, v);
            }
        }.scan(cut, null);
        if (!Objects.equals(span, Arrays.asList("I"))) {
            throw new AssertionError("Unexpected span: " + span);
        }
    }

    @Test //JDK-8259050
    void testBrokenUnicodeEscape() throws IOException {
        String code = "package t;\n" +
                      "class Test {\n" +
                      "    private String s1 = \"\\" + "uaaa\";\n" +
                      "    private String s2 = \\" + "uaaa;\n" +
                      "}\n";
        DiagnosticCollector<JavaFileObject> coll =
                new DiagnosticCollector<>();
        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, coll, null,
                null, Arrays.asList(new MyFileObject(code)));
        CompilationUnitTree cut = ct.parse().iterator().next();
        Trees trees = Trees.instance(ct);
        String ast = cut.toString().replaceAll("\\R", "\n");
        String expected = """
                          package t;

                          class Test {
                              private String s1 = "";
                              private String s2 = (ERROR);
                          } """;
        assertEquals("Unexpected AST, got:\n" + ast, expected, ast);
        List<String> codes = new LinkedList<>();

        for (Diagnostic<? extends JavaFileObject> d : coll.getDiagnostics()) {
            codes.add(d.getCode());
        }

        assertEquals("testBrokenUnicodeEscape: " + codes,
                Arrays.<String>asList("compiler.err.illegal.unicode.esc",
                                      "compiler.err.illegal.unicode.esc"),
                codes);
    }

    @Test //JDK-8259050
    void testUsupportedTextBlock() throws IOException {
        String code = """
                      package t;
                      class Test {
                          private String s = \"""
                                             \""";
                      }""";
        DiagnosticCollector<JavaFileObject> coll =
                new DiagnosticCollector<>();
        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, coll, List.of("--release", "14"),
                null, Arrays.asList(new MyFileObject(code)));
        CompilationUnitTree cut = ct.parse().iterator().next();
        Trees trees = Trees.instance(ct);
        String ast = cut.toString().replaceAll("\\R", "\n");
        String expected = """
                          package t;

                          class Test {
                              private String s = "";
                          } """;
        assertEquals("Unexpected AST, got:\n" + ast, expected, ast);
        List<String> codes = new LinkedList<>();

        for (Diagnostic<? extends JavaFileObject> d : coll.getDiagnostics()) {
            codes.add(d.getCode());
        }

        assertEquals("testUsupportedTextBlock: " + codes,
                Arrays.<String>asList("compiler.err.feature.not.supported.in.source.plural"),
                codes);
    }

    @Test //JDK-8266436
    void testSyntheticConstructorReturnType() throws IOException {
        String code = """
                      package test;
                      public class Test {
                      }
                      """;

        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, null,
                null, null, Arrays.asList(new MyFileObject(code)));
        CompilationUnitTree cut = ct.parse().iterator().next();
        ct.analyze();
        ClassTree clazz = (ClassTree) cut.getTypeDecls().get(0);
        MethodTree constr = (MethodTree) clazz.getMembers().get(0);
        assertEquals("expected null as constructor return type", constr.getReturnType(), null);
    }

    @Test //JDK-8267221
    void testVarArgArrayParameter() throws IOException {
        String code = """
                      package test;
                      public class Test {
                           private void test(int[]... p) {}
                      }
                      """;

        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, null,
                null, null, Arrays.asList(new MyFileObject(code)));
        CompilationUnitTree cut = ct.parse().iterator().next();
        ClassTree clazz = (ClassTree) cut.getTypeDecls().get(0);
        MethodTree constr = (MethodTree) clazz.getMembers().get(0);
        VariableTree param = constr.getParameters().get(0);
        SourcePositions sp = Trees.instance(ct).getSourcePositions();
        int typeStart = (int) sp.getStartPosition(cut, param.getType());
        int typeEnd   = (int) sp.getEndPosition(cut, param.getType());
        assertEquals("correct parameter type span", code.substring(typeStart, typeEnd), "int[]...");
    }

    @Test //JDK-8271928
    void testX() throws IOException {
        String code = """
                      package test;
                          public static void test() {
                              return test;
                          }
                      """;

        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, null,
                null, null, Arrays.asList(new MyFileObject(code)));
        CompilationUnitTree cut = ct.parse().iterator().next();
        SourcePositions sp = Trees.instance(ct).getSourcePositions();
        new TreePathScanner<Void, Void>() {
            @Override
            public Void visitErroneous(ErroneousTree tree, Void p) {
                int pos = (int) sp.getStartPosition(cut, tree);
                if (pos == (-1)) {
                    fail("Invalid source position for an ErroneousTree");
                }
                return scan(tree.getErrorTrees(), p);
            }
        }.scan(cut, null);
    }

    @Test //JDK-8275097
    void testDefaultTagPosition() throws IOException {
        String code = """
                      package t;
                      class Test {
                          private void test1(int i) {
                              switch (i) {
                                  default:
                              }
                          }
                          private void test2(int i) {
                              switch (i) {
                                  case default:
                              }
                          }
                          private int test3(int i) {
                              return switch (i) {
                                  default: yield 0;
                              }
                          }
                          private int test4(int i) {
                              return switch (i) {
                                  case default: yield 0;
                              }
                          }
                          private void test5(int i) {
                              switch (i) {
                                  default -> {}
                              }
                          }
                          private void test6(int i) {
                              switch (i) {
                                  case default -> {}
                              }
                          }
                          private int test5(int i) {
                              return switch (i) {
                                  default -> { yield 0; }
                              }
                          }
                          private int test6(int i) {
                              return switch (i) {
                                  case default -> { yield 0; }
                              }
                          }
                          private int test7(int i) {
                              return switch (i) {
                                  default -> 0;
                              }
                          }
                          private int test8(int i) {
                              return switch (i) {
                                  case default -> 0;
                              }
                          }
                      }
                      """;

        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, null, null,
                null, Arrays.asList(new MyFileObject(code)));
        CompilationUnitTree cut = ct.parse().iterator().next();
        Trees t = Trees.instance(ct);
        SourcePositions sp = t.getSourcePositions();
        new TreeScanner<Void, Void>() {
            @Override
            public Void visitDefaultCaseLabel(DefaultCaseLabelTree tree, Void p) {
                int start = (int) sp.getStartPosition(cut, tree);
                int end   = (int) sp.getEndPosition(cut, tree);
                String defaultName = code.substring(start, end);
                if (!"default".equals(defaultName)) {
                    throw new AssertionError("Incorrect span: " + defaultName);
                }
                return super.visitDefaultCaseLabel(tree, p);
            }

            @Override
            public Void visitCase(CaseTree node, Void p) {
                scan(node.getLabels(), p);
                if (node.getCaseKind() == CaseTree.CaseKind.RULE)
                    scan(node.getBody(), p);
                else
                    scan(node.getStatements(), p);
                return null;
            }
        }.scan(cut, null);
    }

    @Test //JDK-8293897
    void testImplicitFinalInTryWithResources() throws IOException {
        String code = """
                      package t;
                      class Test {
                          void test1() {
                              try (AutoCloseable ac = null) {}
                          }
                          void test2() {
                              try (@Ann AutoCloseable withAnnotation = null) {}
                          }
                          void test3() {
                              try (final AutoCloseable withFinal = null) {}
                          }
                          void test4() {
                              try (final @Ann AutoCloseable withAnnotationFinal = null) {}
                          }
                          @interface Ann {}
                      }
                      """;

        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, null, null,
                null, Arrays.asList(new MyFileObject(code)));
        CompilationUnitTree cut = ct.parse().iterator().next();
        Trees t = Trees.instance(ct);
        SourcePositions sp = t.getSourcePositions();
        new TreeScanner<Void, Void>() {
            boolean modifiersHaveFinal;
            boolean modifiersHaveSpan;

            @Override
            public Void visitVariable(VariableTree node, Void p) {
                boolean prevModifiersHaveFinal = modifiersHaveFinal;
                boolean prevModifiersHaveSpan = modifiersHaveSpan;
                try {
                    modifiersHaveFinal = node.getName().toString().contains("Final");
                    modifiersHaveSpan = modifiersHaveFinal ||
                                        node.getName().toString().contains("Annotation");
                    return super.visitVariable(node, p);
                } finally {
                    modifiersHaveFinal = prevModifiersHaveFinal;
                    modifiersHaveSpan = prevModifiersHaveSpan;
                }
            }
            @Override
            public Void visitClass(ClassTree node, Void p) {
                boolean prevModifiersHaveSpan = modifiersHaveSpan;
                try {
                    modifiersHaveSpan = node.getKind() == Kind.ANNOTATION_TYPE;
                    return super.visitClass(node, p);
                } finally {
                    modifiersHaveSpan = prevModifiersHaveSpan;
                }
            }
            @Override
            public Void visitModifiers(ModifiersTree node, Void p) {
                if (modifiersHaveFinal) {
                    if (!node.getFlags().contains(Modifier.FINAL)) {
                        throw new AssertionError("Expected final missing.");
                    }
                } else {
                    if (node.getFlags().contains(Modifier.FINAL)) {
                        throw new AssertionError("Unexpected final modified.");
                    }
                }
                long start = sp.getStartPosition(cut, node);
                long end = sp.getEndPosition(cut, node);
                if (modifiersHaveSpan) {
                    if (start == (-1) || end == (-1)) {
                        throw new AssertionError("Incorrect modifier span: " + start + "-" + end);
                    }
                } else {
                    if (start != (-1) || end != (-1)) {
                        throw new AssertionError("Incorrect modifier span: " + start + "-" + end);
                    }
                }
                return super.visitModifiers(node, p);
            }
        }.scan(cut, null);
    }

    @Test //JDK-8295401
    void testModuleInfoProvidesRecovery() throws IOException {
        String code = """
                      module m {
                          $DIRECTIVE
                      }
                      """;
        record Test(String directive, int prefix, Kind expectedKind) {}
        Test[] tests = new Test[] {
            new Test("uses api.api.API;", 4, Kind.USES),
            new Test("opens api.api to other.module;", 5, Kind.OPENS),
            new Test("exports api.api to other.module;", 7, Kind.EXPORTS),
            new Test("provides java.util.spi.ToolProvider with impl.ToolProvider;", 8, Kind.PROVIDES),
        };
        JavacTaskPool pool = new JavacTaskPool(1);
        for (Test test : tests) {
            String directive = test.directive();
            for (int i = test.prefix(); i < directive.length(); i++) {
                String replaced = code.replace("$DIRECTIVE", directive.substring(0, i));
                pool.getTask(null, null, d -> {}, List.of(), null, List.of(new MyFileObject(replaced)), task -> {
                    try {
                        CompilationUnitTree cut = task.parse().iterator().next();
                        new TreePathScanner<Void, Void>() {
                            @Override
                            public Void visitModule(ModuleTree node, Void p) {
                                assertEquals("Unexpected directives size: " + node.getDirectives().size(),
                                             node.getDirectives().size(),
                                             1);
                                assertEquals("Unexpected directive: " + node.getDirectives().get(0).getKind(),
                                             node.getDirectives().get(0).getKind(),
                                             test.expectedKind);
                                return super.visitModule(node, p);
                            }
                        }.scan(cut, null);
                        return null;
                    } catch (IOException ex) {
                        throw new IllegalStateException(ex);
                    }
                });
            }
        }
        String extendedCode = """
                              module m {
                                  provides ;
                                  provides java.;
                                  provides java.util.spi.ToolProvider with ;
                                  provides java.util.spi.ToolProvider with impl.;
                              """;
        pool.getTask(null, null, d -> {}, List.of(), null, List.of(new MyFileObject("module-info", extendedCode)), task -> {
            try {
                CompilationUnitTree cut = task.parse().iterator().next();
                task.analyze();
                new TreePathScanner<Void, Void>() {
                    @Override
                    public Void visitModule(ModuleTree node, Void p) {
                        assertEquals("Unexpected directives size: " + node.getDirectives().size(),
                                     node.getDirectives().size(),
                                     4);
                        return super.visitModule(node, p);
                    }
                }.scan(cut, null);
                return null;
            } catch (IOException ex) {
                throw new IllegalStateException(ex);
            }
        });
    }

    @Test //JDK-8304671
    void testEnumConstantUnderscore() throws IOException {
        record TestCase(String code, String release, String ast, String errors) {}
        TestCase[] testCases = new TestCase[] {
            new TestCase("""
                         package t;
                         enum Test {
                             _
                         }
                         """,
                         "8",
                         """
                         package t;
                         \n\
                         enum Test {
                             /*public static final*/ _ /* = new Test() */ /*enum*/ ;
                         } """,
                         """
                         - compiler.warn.option.obsolete.source: 8
                         - compiler.warn.option.obsolete.target: 8
                         - compiler.warn.option.obsolete.suppression
                         Test.java:3:5: compiler.warn.underscore.as.identifier
                         """),
            new TestCase("""
                         package t;
                         enum Test {
                             _
                         }
                         """,
                         System.getProperty("java.specification.version"),
                         """
                         package t;
                         \n\
                         enum Test {
                             /*public static final*/ _ /* = new Test() */ /*enum*/ ;
                         } """,
                         """
                         Test.java:3:5: compiler.err.use.of.underscore.not.allowed.non.variable
                         """),
            new TestCase("""
                         package t;
                         enum Test {
                             _;
                         }
                         """,
                         "8",
                         """
                         package t;
                         \n\
                         enum Test {
                             /*public static final*/ _ /* = new Test() */ /*enum*/ ;
                         } """,
                         """
                         - compiler.warn.option.obsolete.source: 8
                         - compiler.warn.option.obsolete.target: 8
                         - compiler.warn.option.obsolete.suppression
                         Test.java:3:5: compiler.warn.underscore.as.identifier
                         """),
            new TestCase("""
                         package t;
                         enum Test {
                             _;
                         }
                         """,
                         System.getProperty("java.specification.version"),
                         """
                         package t;
                         \n\
                         enum Test {
                             /*public static final*/ _ /* = new Test() */ /*enum*/ ;
                         } """,
                         """
                         Test.java:3:5: compiler.err.use.of.underscore.not.allowed.non.variable
                         """),
            new TestCase("""
                         package t;
                         enum Test {
                             A;
                             void t() {}
                             _;
                         }
                         """,
                         "8",
                         """
                         package t;
                         \n\
                         enum Test {
                             /*public static final*/ A /* = new Test() */ /*enum*/ ,
                             /*public static final*/ _ /* = new Test() */ /*enum*/ ;
                             \n\
                             void t() {
                             }
                         } """,
                         """
                         - compiler.warn.option.obsolete.source: 8
                         - compiler.warn.option.obsolete.target: 8
                         - compiler.warn.option.obsolete.suppression
                         Test.java:5:5: compiler.err.enum.constant.not.expected
                         Test.java:5:5: compiler.warn.underscore.as.identifier
                         """),
            new TestCase("""
                         package t;
                         enum Test {
                             A;
                             void t() {}
                             _;
                         }
                         """,
                         System.getProperty("java.specification.version"),
                         """
                         package t;
                         \n\
                         enum Test {
                             /*public static final*/ A /* = new Test() */ /*enum*/ ,
                             /*public static final*/ _ /* = new Test() */ /*enum*/ ;
                             \n\
                             void t() {
                             }
                         } """,
                         """
                         Test.java:5:5: compiler.err.enum.constant.not.expected
                         """),
            new TestCase("""
                         package t;
                         enum Test {
                             _ {},
                             A;
                         }
                         """,
                         "8",
                         """
                         package t;
                         \n\
                         enum Test {
                             /*public static final*/ _ /* = new Test() */ /*enum*/  {
                             },
                             /*public static final*/ A /* = new Test() */ /*enum*/ ;
                         } """,
                         """
                         - compiler.warn.option.obsolete.source: 8
                         - compiler.warn.option.obsolete.target: 8
                         - compiler.warn.option.obsolete.suppression
                         Test.java:3:5: compiler.warn.underscore.as.identifier
                         """),
            new TestCase("""
                         package t;
                         enum Test {
                             _ {},
                             A;
                         }
                         """,
                         System.getProperty("java.specification.version"),
                         """
                         package t;
                         \n\
                         enum Test {
                             /*public static final*/ _ /* = new Test() */ /*enum*/  {
                             },
                             /*public static final*/ A /* = new Test() */ /*enum*/ ;
                         } """,
                         """
                         Test.java:3:5: compiler.err.use.of.underscore.not.allowed.non.variable
                         """),
        };
        for (TestCase testCase : testCases) {
            StringWriter out = new StringWriter();
            JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(out, fm, null,
                    List.of("-XDrawDiagnostics", "--release", testCase.release),
                    null, Arrays.asList(new MyFileObject(testCase.code)));
            String ast = ct.parse().iterator().next().toString().replaceAll("\\R", "\n");
            assertEquals("Unexpected AST, got:\n" + ast, testCase.ast, ast);
            assertEquals("Unexpected errors, got:\n" + out.toString(),
                         out.toString().replaceAll("\\R", "\n"),
                         testCase.errors);
        }
    }

    @Test
    void testGuardRecovery() throws IOException {
        String code = """
                      package t;
                      class Test {
                          private int t(Integer i, boolean b) {
                              switch (i) {
                                  case 0 when b -> {}
                                  case null when b -> {}
                                  default when b -> {}
                              }
                              return switch (i) {
                                  case 0 when b -> 0;
                                  case null when b -> 0;
                                  default when b -> 0;
                              };
                          }
                      }""";
        DiagnosticCollector<JavaFileObject> coll =
                new DiagnosticCollector<>();
        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, coll, null,
                null, Arrays.asList(new MyFileObject(code)));
        CompilationUnitTree cut = ct.parse().iterator().next();
        new TreeScanner<Void, Void>() {
            @Override
            public Void visitCase(CaseTree node, Void p) {
                assertNotNull(node.getGuard());
                assertEquals("guard kind", Kind.ERRONEOUS, node.getGuard().getKind());
                assertEquals("guard content",
                             List.of("b"),
                             ((ErroneousTree) node.getGuard()).getErrorTrees()
                                                              .stream()
                                                              .map(t -> t.toString()).toList());
                return super.visitCase(node, p);
            }
        }.scan(cut, null);

        List<String> codes = new LinkedList<>();

        for (Diagnostic<? extends JavaFileObject> d : coll.getDiagnostics()) {
            codes.add(d.getLineNumber() + ":" + d.getColumnNumber() + ":" +  d.getCode());
        }

        assertEquals("testUsupportedTextBlock: " + codes,
                List.of("5:20:compiler.err.guard.not.allowed",
                        "6:23:compiler.err.guard.not.allowed",
                        "7:21:compiler.err.guard.not.allowed",
                        "10:20:compiler.err.guard.not.allowed",
                        "11:23:compiler.err.guard.not.allowed",
                        "12:21:compiler.err.guard.not.allowed"),
                codes);
    }

    @Test //JDK-8310326
    void testUnnamedClassPositions() throws IOException {
        //             0         1         2
        //             012345678901234567890
        String code = "void main() { }";
        DiagnosticCollector<JavaFileObject> coll =
                new DiagnosticCollector<>();
        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, coll, null,
                null, Arrays.asList(new MyFileObject(code)));
        Trees trees = Trees.instance(ct);
        SourcePositions sp = trees.getSourcePositions();
        CompilationUnitTree cut = ct.parse().iterator().next();
        new TreeScanner<Void, Void>() {
            @Override
            public Void visitClass(ClassTree node, Void p) {
                assertEquals("Wrong start position", 0, sp.getStartPosition(cut, node));
                assertEquals("Wrong end position", 15, sp.getEndPosition(cut, node));
                assertEquals("Wrong modifiers start position", -1, sp.getStartPosition(cut, node.getModifiers()));
                assertEquals("Wrong modifiers end position", -1, sp.getEndPosition(cut, node.getModifiers()));
                return super.visitClass(node, p);
            }
        }.scan(cut, null);
    }

    @Test //JDK-8312093
    void testJavadoc() throws IOException {
        String code = """
                      public class Test {
                          /***/
                          void main() {
                          }
                      }
                      """;
        DiagnosticCollector<JavaFileObject> coll =
                new DiagnosticCollector<>();
        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, coll, null,
                null, Arrays.asList(new MyFileObject(code)));
        Trees trees = Trees.instance(ct);
        CompilationUnitTree cut = ct.parse().iterator().next();
        new TreePathScanner<Void, Void>() {
            @Override
            public Void visitMethod(MethodTree node, Void p) {
                if (!node.getName().contentEquals("main")) {
                    return null;
                }
                String comment = trees.getDocComment(getCurrentPath());
                assertEquals("Expecting empty comment", "", comment);
                return null;
            }
        }.scan(cut, null);
    }

    @Test //JDK-8312204
    void testDanglingElse() throws IOException {
        String code = """
                      void main() {
                          else ;
                      }
                      """;
        DiagnosticCollector<JavaFileObject> coll =
                new DiagnosticCollector<>();
        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, coll,
                List.of("--enable-preview", "--source", SOURCE_VERSION),
                null, Arrays.asList(new MyFileObject(code)));
        CompilationUnitTree cut = ct.parse().iterator().next();

        String result = cut.toString().replaceAll("\\R", "\n");
        System.out.println("RESULT\n" + result);
        assertEquals("incorrect AST",
                     result,
                     """
                     \n\
                     final class Test {
                         \n\
                         void main() {
                             (ERROR);
                         }
                     }""");

        List<String> codes = new LinkedList<>();

        for (Diagnostic<? extends JavaFileObject> d : coll.getDiagnostics()) {
            codes.add(d.getLineNumber() + ":" + d.getColumnNumber() + ":" + d.getCode());
        }

        assertEquals("testDanglingElse: " + codes,
                     List.of("2:5:compiler.err.else.without.if"),
                     codes);
    }

    @Test //JDK-8315452
    void testPartialTopLevelModifiers() throws IOException {
        String code = """
                      package test;
                      public
                      """;
        DiagnosticCollector<JavaFileObject> coll =
                new DiagnosticCollector<>();
        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, coll,
                List.of("--enable-preview", "--source", SOURCE_VERSION),
                null, Arrays.asList(new MyFileObject(code)));
        CompilationUnitTree cut = ct.parse().iterator().next();

        String result = toStringWithErrors(cut).replaceAll("\\R", "\n");
        System.out.println("RESULT\n" + result);
        assertEquals("incorrect AST",
                     result,
                     """
                     package test;
                     (ERROR: public )""");
    }

    @Test //JDK-8337976
    void testStatementsInClass() throws IOException {
        String code = """
                      package test;
                      public class Test {
                          if (true);
                          while (true);
                          do {} while (true);
                          for ( ; ; );
                          switch (0) { default: }
                          assert true;
                          break;
                          continue;
                          return ;
                          throw new RuntimeException();
                          try {
                          } catch (RuntimeException ex) {}
                      }
                      """;
        DiagnosticCollector<JavaFileObject> coll =
                new DiagnosticCollector<>();
        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, coll,
                List.of("--enable-preview", "--source", SOURCE_VERSION),
                null, Arrays.asList(new MyFileObject(code)));
        CompilationUnitTree cut = ct.parse().iterator().next();

        String result = toStringWithErrors(cut).replaceAll("\\R", "\n");
        System.out.println("RESULT\n" + result);
        assertEquals("incorrect AST",
                     result,
                     """
                     package test;
                     \n\
                     public class Test {
                         (ERROR: if (true) ;)
                         (ERROR: while (true) ;)
                         (ERROR: do {
                     } while (true);)
                         (ERROR: for (; ; ) ;)
                         (ERROR: switch (0) {
                     default:

                     })
                         (ERROR: assert true;)
                         (ERROR: break;)
                         (ERROR: continue;)
                         (ERROR: return;)
                         (ERROR: throw new RuntimeException();)
                         (ERROR: try {
                     } catch (RuntimeException ex) {
                     })
                     }""");

        List<String> codes = new LinkedList<>();

        for (Diagnostic<? extends JavaFileObject> d : coll.getDiagnostics()) {
            codes.add(d.getLineNumber() + ":" + d.getColumnNumber() + ":" + d.getCode());
        }

        assertEquals("testStatementsInClass: " + codes,
                     List.of("3:5:compiler.err.statement.not.expected",
                             "4:5:compiler.err.statement.not.expected",
                             "5:5:compiler.err.statement.not.expected",
                             "6:5:compiler.err.statement.not.expected",
                             "7:5:compiler.err.statement.not.expected",
                             "8:5:compiler.err.statement.not.expected",
                             "9:5:compiler.err.statement.not.expected",
                             "10:5:compiler.err.statement.not.expected",
                             "11:5:compiler.err.statement.not.expected",
                             "12:5:compiler.err.statement.not.expected",
                             "13:5:compiler.err.statement.not.expected"),
                     codes);
    }

    @Test //JDK-8324859
    void testImplicitlyDeclaredClassesConfusion1() throws IOException {
        String code = """
                      package tests;
                      public class TestB {
                          public static boolean test() // missing open brace
                              return true;
                          }
                          public static boolean test2() {
                              return true;
                          }
                      }""";
        DiagnosticCollector<JavaFileObject> coll =
                new DiagnosticCollector<>();
        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, coll,
                List.of("--enable-preview", "--source", SOURCE_VERSION),
                null, Arrays.asList(new MyFileObject(code)));
        CompilationUnitTree cut = ct.parse().iterator().next();

        List<String> codes = new LinkedList<>();

        for (Diagnostic<? extends JavaFileObject> d : coll.getDiagnostics()) {
            codes.add(d.getLineNumber() + ":" + d.getColumnNumber() + ":" + d.getCode());
        }

        assertEquals("testImplicitlyDeclaredClassesConfusion1: " + codes,
                     List.of("3:33:compiler.err.expected2"),
                     codes);
        String result = toStringWithErrors(cut).replaceAll("\\R", "\n");
        System.out.println("RESULT\n" + result);
        assertEquals("incorrect AST",
                     result,
                     """
                     package tests;
                     \n\
                     public class TestB {
                         \n\
                         public static boolean test() {
                             return true;
                         }
                         \n\
                         public static boolean test2() {
                             return true;
                         }
                     }""");
    }

    @Test //JDK-8324859
    void testImplicitlyDeclaredClassesConfusion2() throws IOException {
        String code = """
                      package tests;
                      public class TestB {
                          public static boolean test() // missing open brace

                          public static boolean test2() {
                              return true;
                          }
                      }                       """;
        DiagnosticCollector<JavaFileObject> coll =
                new DiagnosticCollector<>();
        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, coll,
                List.of("--enable-preview", "--source", SOURCE_VERSION),
                null, Arrays.asList(new MyFileObject(code)));
        CompilationUnitTree cut = ct.parse().iterator().next();

        List<String> codes = new LinkedList<>();

        for (Diagnostic<? extends JavaFileObject> d : coll.getDiagnostics()) {
            codes.add(d.getLineNumber() + ":" + d.getColumnNumber() + ":" + d.getCode());
        }

        assertEquals("testImplicitlyDeclaredClassesConfusion2: " + codes,
                     List.of("3:33:compiler.err.expected2"),
                     codes);
        String result = toStringWithErrors(cut).replaceAll("\\R", "\n");
        System.out.println("RESULT\n" + result);
        assertEquals("incorrect AST",
                     result,
                     """
                     package tests;
                     \n\
                     public class TestB {
                         \n\
                         public static boolean test();
                         \n\
                         public static boolean test2() {
                             return true;
                         }
                     }""");
    }

    @Test //JDK-8324859
    void testImplicitlyDeclaredClassesConfusion3() throws IOException {
        String code = """
                      package tests;
                      public class TestB {
                          public static boolean test() // missing open brace
                      }
                      """;
        DiagnosticCollector<JavaFileObject> coll =
                new DiagnosticCollector<>();
        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, coll,
                List.of("--enable-preview", "--source", SOURCE_VERSION),
                null, Arrays.asList(new MyFileObject(code)));
        CompilationUnitTree cut = ct.parse().iterator().next();

        List<String> codes = new LinkedList<>();

        for (Diagnostic<? extends JavaFileObject> d : coll.getDiagnostics()) {
            codes.add(d.getLineNumber() + ":" + d.getColumnNumber() + ":" + d.getCode());
        }

        assertEquals("testImplicitlyDeclaredClassesConfusion3: " + codes,
                     List.of("3:33:compiler.err.expected2"),
                     codes);
        String result = toStringWithErrors(cut).replaceAll("\\R", "\n");
        System.out.println("RESULT\n" + result);
        assertEquals("incorrect AST",
                     result,
                     """
                     package tests;
                     \n\
                     public class TestB {
                         \n\
                         public static boolean test();
                     }""");
    }

    @Test //JDK-8324859
    void testImplicitlyDeclaredClassesConfusion4() throws IOException {
        String code = """
                      package tests;
                      public class TestB {
                          public static boolean test() // missing open brace
                          }
                      }
                      """;
        DiagnosticCollector<JavaFileObject> coll =
                new DiagnosticCollector<>();
        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, coll,
                List.of("--enable-preview", "--source", SOURCE_VERSION),
                null, Arrays.asList(new MyFileObject(code)));
        CompilationUnitTree cut = ct.parse().iterator().next();

        List<String> codes = new LinkedList<>();

        for (Diagnostic<? extends JavaFileObject> d : coll.getDiagnostics()) {
            codes.add(d.getLineNumber() + ":" + d.getColumnNumber() + ":" + d.getCode());
        }

        assertEquals("testImplicitlyDeclaredClassesConfusion4: " + codes,
                     List.of("3:33:compiler.err.expected2"),
                     codes);
        String result = toStringWithErrors(cut).replaceAll("\\R", "\n");
        System.out.println("RESULT\n" + result);
        assertEquals("incorrect AST",
                     result,
                     """
                     package tests;
                     \n\
                     public class TestB {
                         \n\
                         public static boolean test() {
                         }
                     }""");
    }

    @Test //JDK-8324859
    void testImplicitlyDeclaredClassesConfusion5() throws IOException {
        String code = """
                      package tests;
                      public class TestB {
                          public static boolean test(String,
                      }
                      class T {}
                      """;
        DiagnosticCollector<JavaFileObject> coll =
                new DiagnosticCollector<>();
        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, coll,
                List.of("--enable-preview", "--source", SOURCE_VERSION),
                null, Arrays.asList(new MyFileObject(code)));
        CompilationUnitTree cut = ct.parse().iterator().next();

        List<String> codes = new LinkedList<>();

        for (Diagnostic<? extends JavaFileObject> d : coll.getDiagnostics()) {
            codes.add(d.getLineNumber() + ":" + d.getColumnNumber() + ":" + d.getCode());
        }

        assertEquals("testImplicitlyDeclaredClassesConfusion5: " + codes,
                     List.of("3:38:compiler.err.expected",
                             "4:1:compiler.err.illegal.start.of.type"),
                     codes);
        String result = toStringWithErrors(cut).replaceAll("\\R", "\n");
        System.out.println("RESULT\n" + result);
        assertEquals("incorrect AST",
                     result,
                     """
                     package tests;
                     \n\
                     public class TestB {
                         \n\
                         public static boolean test(String <error>, (ERROR: ) <error>);
                     }
                     class T {
                     }""");
    }

    @Test //JDK-8324859
    void testImplicitlyDeclaredClassesConfusion6() throws IOException {
        String code = """
                      package tests;
                      public class TestB {
                          private Object testMethod(final String arg1 final String arg2) {
                              return null;
                          }
                      }
                      """;
        DiagnosticCollector<JavaFileObject> coll =
                new DiagnosticCollector<>();
        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, coll,
                List.of("--enable-preview", "--source", SOURCE_VERSION),
                null, Arrays.asList(new MyFileObject(code)));
        CompilationUnitTree cut = ct.parse().iterator().next();

        List<String> codes = new LinkedList<>();

        for (Diagnostic<? extends JavaFileObject> d : coll.getDiagnostics()) {
            codes.add(d.getLineNumber() + ":" + d.getColumnNumber() + ":" + d.getCode());
        }

        assertEquals("testImplicitlyDeclaredClassesConfusion5: " + codes,
                     List.of("3:48:compiler.err.expected3",
                             "3:66:compiler.err.expected"),
                     codes);
        String result = toStringWithErrors(cut).replaceAll("\\R", "\n");
        System.out.println("RESULT\n" + result);
        assertEquals("incorrect AST",
                     result,
                     """
                     package tests;
                     \n\
                     public class TestB {
                         \n\
                         private Object testMethod(final String arg1);
                         final String arg2;
                         {
                             return null;
                         }
                     }""");
    }

    @Test //JDK-8324859
    void testImplicitlyDeclaredClassesConfusion7() throws IOException {
        //after 'default' attribute value, only semicolon (';') is expected,
        //not left brace ('{'):
        String code = """
                      package tests;
                      public @interface A {
                          public String value() default ""
                      }
                      """;
        DiagnosticCollector<JavaFileObject> coll =
                new DiagnosticCollector<>();
        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, coll,
                List.of("--enable-preview", "--source", SOURCE_VERSION),
                null, Arrays.asList(new MyFileObject(code)));
        CompilationUnitTree cut = ct.parse().iterator().next();

        List<String> codes = new LinkedList<>();

        for (Diagnostic<? extends JavaFileObject> d : coll.getDiagnostics()) {
            codes.add(d.getLineNumber() + ":" + d.getColumnNumber() + ":" + d.getCode());
        }

        assertEquals("testImplicitlyDeclaredClassesConfusion5: " + codes,
                     List.of("3:37:compiler.err.expected"),
                     codes);
        String result = toStringWithErrors(cut).replaceAll("\\R", "\n");
        System.out.println("RESULT\n" + result);
        assertEquals("incorrect AST",
                     result,
                     """
                     package tests;
                     \n\
                     public @interface A {
                         \n\
                         public String value() default "";
                     }""");
    }

    @Test //JDK-8324859
    void testImplicitlyDeclaredClassesConfusion10() throws IOException {
        String code = """
                      package tests;
                      public class TestB {
                          public static boolean test() // missing open brace
                              String s = "";
                              return s.isEmpty();
                          }
                          public static boolean test2() {
                              return true;
                          }
                      }""";
        DiagnosticCollector<JavaFileObject> coll =
                new DiagnosticCollector<>();
        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, coll,
                List.of("--enable-preview", "--source", SOURCE_VERSION),
                null, Arrays.asList(new MyFileObject(code)));
        CompilationUnitTree cut = ct.parse().iterator().next();

        List<String> codes = new LinkedList<>();

        for (Diagnostic<? extends JavaFileObject> d : coll.getDiagnostics()) {
            codes.add(d.getLineNumber() + ":" + d.getColumnNumber() + ":" + d.getCode());
        }

        assertEquals("testImplicitlyDeclaredClassesConfusion1: " + codes,
                     List.of("3:33:compiler.err.expected2"),
                     codes);
        String result = toStringWithErrors(cut).replaceAll("\\R", "\n");
        System.out.println("RESULT\n" + result);
        assertEquals("incorrect AST",
                     result,
                     """
                     package tests;

                     public class TestB {
                         \n\
                         public static boolean test() {
                             String s = "";
                             return s.isEmpty();
                         }
                         \n\
                         public static boolean test2() {
                             return true;
                         }
                     }""");
    }

    @Test //JDK-8324859
    void testImplicitlyDeclaredClassesConfusion11() throws IOException {
        String code = """
                      package tests;
                      public class TestB {
                          public static boolean test() // missing open brace
                          String s = ""; //field declaration
                          public static boolean test2() {
                              return true;
                          }
                      }""";
        DiagnosticCollector<JavaFileObject> coll =
                new DiagnosticCollector<>();
        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, coll,
                List.of("--enable-preview", "--source", SOURCE_VERSION),
                null, Arrays.asList(new MyFileObject(code)));
        CompilationUnitTree cut = ct.parse().iterator().next();

        List<String> codes = new LinkedList<>();

        for (Diagnostic<? extends JavaFileObject> d : coll.getDiagnostics()) {
            codes.add(d.getLineNumber() + ":" + d.getColumnNumber() + ":" + d.getCode());
        }

        assertEquals("testImplicitlyDeclaredClassesConfusion1: " + codes,
                     List.of("3:33:compiler.err.expected2"),
                     codes);
        String result = toStringWithErrors(cut).replaceAll("\\R", "\n");
        System.out.println("RESULT\n" + result);
        assertEquals("incorrect AST",
                     result,
                     """
                     package tests;
                     \n\
                     public class TestB {
                         \n\
                         public static boolean test();
                         String s = "";
                         \n\
                         public static boolean test2() {
                             return true;
                         }
                     }""");
    }

    @Test //JDK-8324859
    void testImplicitlyDeclaredClassesConfusion12() throws IOException {
        String code = """
                      package tests;
                      public class TestB {
                          public static boolean test() // missing open brace
                              final String s = "";
                              return s.isEmpty();
                          }
                          public static boolean test2() {
                              return true;
                          }
                      }""";
        DiagnosticCollector<JavaFileObject> coll =
                new DiagnosticCollector<>();
        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, coll,
                List.of("--enable-preview", "--source", SOURCE_VERSION),
                null, Arrays.asList(new MyFileObject(code)));
        CompilationUnitTree cut = ct.parse().iterator().next();

        List<String> codes = new LinkedList<>();

        for (Diagnostic<? extends JavaFileObject> d : coll.getDiagnostics()) {
            codes.add(d.getLineNumber() + ":" + d.getColumnNumber() + ":" + d.getCode());
        }

        assertEquals("testImplicitlyDeclaredClassesConfusion1: " + codes,
                     List.of("3:33:compiler.err.expected2"),
                     codes);
        String result = toStringWithErrors(cut).replaceAll("\\R", "\n");
        System.out.println("RESULT\n" + result);
        assertEquals("incorrect AST",
                     result,
                     """
                     package tests;
                     \n\
                     public class TestB {
                         \n\
                         public static boolean test() {
                             final String s = "";
                             return s.isEmpty();
                         }
                         \n\
                         public static boolean test2() {
                             return true;
                         }
                     }""");
    }

    @Test //JDK-8324859
    void testImplicitlyDeclaredClassesConfusion13() throws IOException {
        String code = """
                      package tests;
                      public class TestB {
                          public static boolean test() // missing open brace
                          final String s = ""; //field declaration?
                          public static boolean test2() {
                              return true;
                          }
                      }""";
        DiagnosticCollector<JavaFileObject> coll =
                new DiagnosticCollector<>();
        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, coll,
                List.of("--enable-preview", "--source", SOURCE_VERSION),
                null, Arrays.asList(new MyFileObject(code)));
        CompilationUnitTree cut = ct.parse().iterator().next();

        List<String> codes = new LinkedList<>();

        for (Diagnostic<? extends JavaFileObject> d : coll.getDiagnostics()) {
            codes.add(d.getLineNumber() + ":" + d.getColumnNumber() + ":" + d.getCode());
        }

        assertEquals("testImplicitlyDeclaredClassesConfusion1: " + codes,
                     List.of("3:33:compiler.err.expected2"),
                     codes);
        String result = toStringWithErrors(cut).replaceAll("\\R", "\n");
        System.out.println("RESULT\n" + result);
        assertEquals("incorrect AST",
                     result,
                     """
                     package tests;
                     \n\
                     public class TestB {
                         \n\
                         public static boolean test();
                         final String s = "";
                         \n\
                         public static boolean test2() {
                             return true;
                         }
                     }""");
    }

    @Test //JDK-8324859
    void testImplicitlyDeclaredClassesConfusion14() throws IOException {
        String code = """
                      package tests;
                      public class TestB {
                          public static boolean test() // missing open brace
                              String s = "";
                              s.length();
                              if (true); //force parse as block
                          public static boolean test2() {
                              return true;
                          }
                      }""";
        DiagnosticCollector<JavaFileObject> coll =
                new DiagnosticCollector<>();
        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, coll,
                List.of("--enable-preview", "--source", SOURCE_VERSION),
                null, Arrays.asList(new MyFileObject(code)));
        CompilationUnitTree cut = ct.parse().iterator().next();

        List<String> codes = new LinkedList<>();

        for (Diagnostic<? extends JavaFileObject> d : coll.getDiagnostics()) {
            codes.add(d.getLineNumber() + ":" + d.getColumnNumber() + ":" + d.getCode());
        }

        assertEquals("testImplicitlyDeclaredClassesConfusion1: " + codes,
                     List.of("3:33:compiler.err.expected2",
                             "7:5:compiler.err.illegal.start.of.expr"),
                     codes);
        String result = toStringWithErrors(cut).replaceAll("\\R", "\n");
        System.out.println("RESULT\n" + result);
        assertEquals("incorrect AST",
                     result,
                     """
                     package tests;
                     \n\
                     public class TestB {
                         \n\
                         public static boolean test() {
                             String s = "";
                             s.length();
                             if (true) ;
                             (ERROR: );
                         }
                         \n\
                         public static boolean test2() {
                             return true;
                         }
                     }""");
    }

    @Test //JDK-8351260
    void testVeryBrokenTypeWithAnnotations() throws IOException {
        String code = """
                      package tests;
                      class ListUtilsTest {
                          void test(List<@AlphaChars <@StringLength(int value = 5)String> s){
                          }
                      }
                      """;
        DiagnosticCollector<JavaFileObject> coll =
                new DiagnosticCollector<>();
        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, coll,
                List.of("--enable-preview", "--source", SOURCE_VERSION),
                null, Arrays.asList(new MyFileObject(code)));
        CompilationUnitTree cut = ct.parse().iterator().next();

        List<String> codes = new LinkedList<>();

        for (Diagnostic<? extends JavaFileObject> d : coll.getDiagnostics()) {
            codes.add(d.getLineNumber() + ":" + d.getColumnNumber() + ":" + d.getCode());
        }

        assertEquals("testVeryBrokenTypeWithAnnotations: " + codes,
                     List.of("3:32:compiler.err.illegal.start.of.type",
                             "3:51:compiler.err.dot.class.expected",
                             "3:57:compiler.err.expected2",
                             "3:60:compiler.err.expected2",
                             "3:61:compiler.err.expected2",
                             "3:67:compiler.err.not.stmt",
                             "3:70:compiler.err.expected",
                             "5:2:compiler.err.premature.eof"),
                     codes);
        String result = toStringWithErrors(cut).replaceAll("\\R", "\n");
        System.out.println("RESULT\n" + result);
        assertEquals("incorrect AST",
                     result,
                     """
                     package tests;
                     \n\
                     class ListUtilsTest {
                         \n\
                         void test(List<@AlphaChars (ERROR: (ERROR)<@StringLength(int) value, (ERROR)> = 5), (ERROR: )> <error>) {
                             (ERROR: String > s);
                             {
                             }
                         }
                     }""");
    }

    @Test //JDK-8351260
    void testVeryBrokenTypeWithAnnotationsMinimal() throws IOException {
        String code = """
                      B<@C<@D(e f=
                      """;
        DiagnosticCollector<JavaFileObject> coll =
                new DiagnosticCollector<>();
        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, coll,
                List.of("--enable-preview", "--source", SOURCE_VERSION),
                null, Arrays.asList(new MyFileObject(code)));
        //no exceptions:
        ct.parse().iterator().next();
    }

    @Test //JDK-8370865
    void testCompactSourceFileMultiField1() throws IOException {
        String code = """
                      int i, j, k;
                      void main() {}
                      """;
        DiagnosticCollector<JavaFileObject> coll =
                new DiagnosticCollector<>();
        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, coll,
                List.of("-XDrawDiagnostics"),
                null, Arrays.asList(new MyFileObject(code)));
        //no exceptions:
        ct.parse().iterator().next();
        List<String> codes = new LinkedList<>();

        for (Diagnostic<? extends JavaFileObject> d : coll.getDiagnostics()) {
            codes.add(d.getLineNumber() + ":" + d.getColumnNumber() + ":" + d.getCode());
        }

        assertEquals("testCompactSourceFileMultiField1: " + codes,
                     List.of(),
                     codes);
    }

    @Test //JDK-8370865
    void testCompactSourceFileMultiField2() throws IOException {
        String code = """
                      int i, j = 0, k = 0;
                      void main() {}
                      """;
        DiagnosticCollector<JavaFileObject> coll =
                new DiagnosticCollector<>();
        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, coll,
                List.of("-XDrawDiagnostics"),
                null, Arrays.asList(new MyFileObject(code)));
        //no exceptions:
        ct.parse().iterator().next();
        List<String> codes = new LinkedList<>();

        for (Diagnostic<? extends JavaFileObject> d : coll.getDiagnostics()) {
            codes.add(d.getLineNumber() + ":" + d.getColumnNumber() + ":" + d.getCode());
        }

        assertEquals("testCompactSourceFileMultiField2: " + codes,
                     List.of(),
                     codes);
    }

    @Test //JDK-8369489
    void testTypeAnnotationBrokenMethodRef() throws IOException {
        String code = """
                      public class Test {
                          Object o1 = @Ann any()::test;
                          Object o2 = @Ann any().field::test;
                      }
                      """;
        DiagnosticCollector<JavaFileObject> coll =
                new DiagnosticCollector<>();
        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, coll,
                null,
                null, Arrays.asList(new MyFileObject(code)));
        //no exceptions:
        ct.parse().iterator().next();
        List<String> codes = new LinkedList<>();

        for (Diagnostic<? extends JavaFileObject> d : coll.getDiagnostics()) {
            codes.add(d.getLineNumber() + ":" + d.getColumnNumber() + ":" + d.getCode());
        }

        assertEquals("testTypeAnnotationBrokenMethodRef: " + codes,
                     List.of("2:22:compiler.err.illegal.start.of.type",
                             "3:22:compiler.err.illegal.start.of.type"),
                     codes);
    }

    @Test
    void testVarPositions() throws IOException {
        String code = """
                      public class Test {
                          void t() {
                              var v =
                          }
                      }
                      """;
        DiagnosticCollector<JavaFileObject> coll =
                new DiagnosticCollector<>();
        JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, coll,
                null,
                null, Arrays.asList(new MyFileObject(code)));
        Trees trees = Trees.instance(ct);
        SourcePositions sp = trees.getSourcePositions();
        CompilationUnitTree cut = ct.parse().iterator().next();
        new TreeScanner<Void, Void>() {
            @Override
            public Void visitVarType(VarTypeTree node, Void p) {
                long start = sp.getStartPosition(cut, node);
                long end = sp.getEndPosition(cut, node);
                String text = code.substring((int) start, (int) end);
                assertEquals("var start pos",
                             "var",
                             text);
                return null;
            }
        };
    }

    void run(String[] args) throws Exception {
        int passed = 0, failed = 0;
        final Pattern p = (args != null && args.length > 0)
                ? Pattern.compile(args[0])
                : null;
        for (Method m : this.getClass().getDeclaredMethods()) {
            boolean selected = (p == null)
                    ? m.isAnnotationPresent(Test.class)
                    : p.matcher(m.getName()).matches();
            if (selected) {
                try {
                    m.invoke(this, (Object[]) null);
                    System.out.println(m.getName() + ": OK");
                    passed++;
                } catch (Throwable ex) {
                    System.out.printf("Test %s failed: %s %n", m, ex.getCause());
                    failed++;
                }
            }
        }
        System.out.printf("Passed: %d, Failed %d%n", passed, failed);
        if (failed > 0) {
            throw new RuntimeException("Tests failed: " + failed);
        }
        if (passed == 0 && failed == 0) {
            throw new AssertionError("No test(s) selected: passed = " +
                    passed + ", failed = " + failed + " ??????????");
        }
    }

    private String toStringWithErrors(Tree tree) {
        StringWriter s = new StringWriter();
        try {
            new PrettyWithErrors(s, false).printExpr((JCTree) tree);
        } catch (IOException e) {
            // should never happen, because StringWriter is defined
            // never to throw any IOExceptions
            throw new AssertionError(e);
        }
        return s.toString();
    }

    private static final class PrettyWithErrors extends Pretty {

        public PrettyWithErrors(Writer out, boolean sourceOutput) {
            super(out, sourceOutput);
        }

        @Override
        public void visitErroneous(JCErroneous tree) {
            try {
                print("(ERROR: ");
                print(tree.errs);
                print(")");
            } catch (IOException e) {
                throw new UncheckedIOException(e);
            }
        }

    }

}

abstract class TestCase {

    void assertEquals(String message, int i, int pos) {
        if (i != pos) {
            fail(message);
        }
    }

    void assertFalse(String message, boolean bvalue) {
        if (bvalue == true) {
            fail(message);
        }
    }

    void assertTrue(String message, boolean bvalue) {
        if (bvalue == false) {
            fail(message);
        }
    }

    void assertEquals(String message, int i, long l) {
        if (i != l) {
            fail(message + ":" + i + ":" + l);
        }
    }

    void assertEquals(String message, Object o1, Object o2) {
        if (!Objects.equals(o1, o2)) {
            fail(message);
        }
    }

    void assertNotNull(Object o) {
        if (o == null) {
            fail();
        }
    }

    void fail() {
        fail("test failed");
    }

    void fail(String message) {
        throw new RuntimeException(message);
    }

    /**
     * Indicates that the annotated method is a test method.
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface Test {}
}
