diff --git a/com.sap.adt.abapcleaner/src/com/sap/adt/abapcleaner/parser/Command.java b/com.sap.adt.abapcleaner/src/com/sap/adt/abapcleaner/parser/Command.java index a53956b0..b4fb1a8d 100644 --- a/com.sap.adt.abapcleaner/src/com/sap/adt/abapcleaner/parser/Command.java +++ b/com.sap.adt.abapcleaner/src/com/sap/adt/abapcleaner/parser/Command.java @@ -319,14 +319,19 @@ final void addOpener(LevelOpener opener) { openers.put(key, opener); } - final String getErrorMessageForMissingOpener() { - StringBuilder openersList = new StringBuilder(); + final String getOpenersList() { return getOpenersList("/"); } + final String getOpenersList(String separator) { + StringBuilder result = new StringBuilder(); for (Map.Entry kvp : openers.entrySet()) { - if (openersList.length() > 0) - openersList.append("/"); - openersList.append(kvp.getValue().text); + if (result.length() > 0) + result.append("/"); + result.append(kvp.getValue().text); } - return text + " found, but no corresponding " + openersList; + return result.toString(); + } + + final boolean isCloserFor(LevelOpener levelOpener) { + return getOpeners().containsKey(getLevelCloserKey(levelOpener.text)); } } @@ -521,15 +526,34 @@ final void addNext(Command newCommand) throws UnexpectedSyntaxException { newCommand.initialBlockLevel = initialBlockLevel + blockLevelDiff; - if (getOpensLevel() && !newCommand.getClosesLevel()) { - addChild(newCommand); - } else if (!getOpensLevel() && newCommand.getClosesLevel()) { - if (parent != null) + if (getOpensLevel()) { + if (!newCommand.getClosesLevel()) { + addChild(newCommand); + } else if (newCommand.usedLevelCloser.isCloserFor(usedLevelOpener)) { + // newCommand directly closes this Command + addSibling(newCommand); + } else if (newCommand.usedLevelCloser.requiresOpener) { + throw new UnexpectedSyntaxException(this, + "expected " + usedLevelOpener.getClosersList() + ", but found " + newCommand.usedLevelCloser.text + ". " + + "Opening command (line " + Cult.format(this.sourceLineNumStart) + "): " + this.toStringForErrorMessage()); + } else { + // remove an optional level closer that does not apply in this case, e.g. 'DEFINE any_macro. CLASS &1 DEFINITION.' + newCommand.usedLevelCloser = null; + addChild(newCommand); + } + + } else if (newCommand.getClosesLevel()) { + if (parent != null && newCommand.usedLevelCloser.isCloserFor(parent.usedLevelOpener)) { parent.addSibling(newCommand); - else if (newCommand.usedLevelCloser.requiresOpener) - throw new UnexpectedSyntaxException(this, newCommand.usedLevelCloser.getErrorMessageForMissingOpener()); - else + } else if (newCommand.usedLevelCloser.requiresOpener) { + throw new UnexpectedSyntaxException(this, + newCommand.usedLevelCloser.text + " found in line " + Cult.format(newCommand.sourceLineNumStart) + + ", but no corresponding " + newCommand.usedLevelCloser.getOpenersList()); + } else { newCommand.usedLevelCloser = null; + addSibling(newCommand); + } + } else { addSibling(newCommand); } @@ -557,14 +581,6 @@ else if (newCommand.usedLevelCloser.requiresOpener) } private void addSibling(Command newCommand) throws UnexpectedSyntaxException { - if (newCommand.getClosesLevel()) { - if (!newCommand.usedLevelCloser.getOpeners().containsKey(getLevelCloserKey(usedLevelOpener.text))) { - throw new UnexpectedSyntaxException(this, - "expected " + usedLevelOpener.getClosersList() + ", but found " + newCommand.usedLevelCloser.text + ". " + - "Opening command (line " + Cult.format(this.sourceLineNumStart) + "): " + this.toStringForErrorMessage()); - } - } - newCommand.parent = parent; newCommand.prevSibling = this; diff --git a/test/com.sap.adt.abapcleaner.test/src/com/sap/adt/abapcleaner/parser/CodeTest.java b/test/com.sap.adt.abapcleaner.test/src/com/sap/adt/abapcleaner/parser/CodeTest.java index a2643f2a..7da64e26 100644 --- a/test/com.sap.adt.abapcleaner.test/src/com/sap/adt/abapcleaner/parser/CodeTest.java +++ b/test/com.sap.adt.abapcleaner.test/src/com/sap/adt/abapcleaner/parser/CodeTest.java @@ -1081,4 +1081,26 @@ void testWriteAt() { testParseCode(); } + + @Test + void testDefineWithClassDefinition() { + // ensure that CLASS can be nested in DEFINE: CLASS is an optional level closer in the context of a REPORT, + // but does NOT close a level in this case + + buildSrc("DEFINE any_macro."); + buildSrc(" \" comment"); + buildSrc(" CLASS &1 DEFINITION FOR TESTING INHERITING FROM cx_static_check."); + buildSrc(" ENDCLASS."); + buildSrc("END-OF-DEFINITION."); + buildSrc(""); + buildSrc("DEFINE other_macro."); + buildSrc(" CLASS &1 DEFINITION FOR TESTING INHERITING FROM cx_static_check."); + buildSrc(" ENDCLASS."); + buildSrc(""); + buildSrc(" CLASS &1 IMPLEMENTATION."); + buildSrc(" ENDCLASS."); + buildSrc("END-OF-DEFINITION."); + + testParseCode(); + } }