Skip to content

Commit 17ae5c2

Browse files
committed
enhance AlignDeclarationsRule for generic structures
1 parent d9d5397 commit 17ae5c2

File tree

4 files changed

+411
-10
lines changed

4 files changed

+411
-10
lines changed

com.sap.adt.abapcleaner/src/com/sap/adt/abapcleaner/rules/alignment/AlignDeclarationsRule.java

Lines changed: 141 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,16 @@ private enum TableType {
3333

3434
private static final int MAX_COLUMN_COUNT = 8;
3535

36+
/** for the components after TYPE ANY STRUCTURE CONTAINING */
37+
private enum ComponentColumns {
38+
COMPONENT_NAME,
39+
TYPE;
40+
41+
public int getValue() { return this.ordinal(); }
42+
}
43+
44+
private static final int MAX_COMPONENT_COLUMN_COUNT = 2;
45+
3646
private final static RuleReference[] references = new RuleReference[] {
3747
new RuleReference(RuleSource.ABAP_CLEANER),
3848
new RuleReference(RuleSource.ABAP_STYLE_GUIDE, "Don't align type clauses", "#dont-align-type-clauses", true) };
@@ -220,6 +230,14 @@ public String getExample() {
220230
+ LINE_SEP + " two VALUE 2,"
221231
+ LINE_SEP + " three VALUE 3,"
222232
+ LINE_SEP + " END OF ENUM number."
233+
+ LINE_SEP + ""
234+
+ LINE_SEP + " \" for readability, definitions of generic structures should not be part of a"
235+
+ LINE_SEP + " \" TYPES: chain; with that, CONTAINING can even be moved before the type name"
236+
+ LINE_SEP + " TYPES gen_struc TYPE ANY"
237+
+ LINE_SEP + " STRUCTURE CONTAINING %cid_ref TYPE abp_behv_cid"
238+
+ LINE_SEP + " %is_draft TYPE"
239+
+ LINE_SEP + " abp_behv_flag other_struc TYPE"
240+
+ LINE_SEP + " ANY STRUCTURE."
223241
+ LINE_SEP
224242
+ LINE_SEP + " \" if maximum line length is exceeded, VALUE clauses can be moved below TYPE or even below the name"
225243
+ LINE_SEP + " CONSTANTS lc_any_constant_with_long_name TYPE if_any_interface=>ty_any_type VALUE if_any_interface=>co_any_value_with_long_name."
@@ -232,6 +250,7 @@ public String getExample() {
232250
private final String[] alignNonChainsActionTexts = new String[] { "align name, TYPE, LENGTH, VALUE etc. if filled", "align name and TYPE", "align name only (like Pretty Printer)" };
233251
private final String[] alignStructureActionTexts = new String[] { "align name, TYPE, LENGTH, VALUE etc. if filled", "align name and TYPE (like Pretty Printer)", "align name only" };
234252
private final String[] alignEnumActionTexts = new String[] { "align name and VALUE (like Pretty Printer)", "align name only" };
253+
private final String[] containingPositionTexts = new String[] { "keep as is", "continue after ANY STRUCTURE", "continue after ANY STRUCTURE, then break", "below TYPES keyword + 2", "below type name + 2", "below TYPE", "below ANY STRUCTURE" };
235254

236255
private final String[] structureAlignStyleTexts = new String[] { "align outer structure with inner", "align outer structure independently (like Pretty Printer)", "align each section independently" };
237256

@@ -242,11 +261,12 @@ public String getExample() {
242261
final ConfigEnumValue<AlignDeclarationsAction> configAlignStructureAction = new ConfigEnumValue<AlignDeclarationsAction>(this, "AlignStructureAction", "Action for structures (BEGIN OF ...):", alignStructureActionTexts, AlignDeclarationsAction.values(), AlignDeclarationsAction.ALIGN_NAME_TYPE_LENGTH_ETC, AlignDeclarationsAction.ALIGN_NAME_TYPE_LENGTH_ETC, LocalDate.of(2023, 6, 10) );
243262
final ConfigEnumValue<StructureAlignStyle> configStructureAlignStyle = new ConfigEnumValue<StructureAlignStyle>(this, "StructureAlignStyle", "Scope of nested structures:", structureAlignStyleTexts, StructureAlignStyle.values(), StructureAlignStyle.PER_LEVEL, StructureAlignStyle.ACROSS_LEVELS, LocalDate.of(2023, 5, 21) );
244263
final ConfigEnumValue<AlignEnumAction> configAlignEnumAction = new ConfigEnumValue<AlignEnumAction>(this, "AlignEnumAction", "Action for enums (BEGIN OF ENUM ...):", alignEnumActionTexts, AlignEnumAction.values(), AlignEnumAction.ALIGN_NAME_AND_VALUE, AlignEnumAction.ALIGN_NAME_ONLY, LocalDate.of(2023, 12, 30) );
264+
final ConfigEnumValue<ContainingPosition> configContainingPosition = new ConfigEnumValue<ContainingPosition>(this, "ContainingPosition", "Position of CONTAINING for generic structures:", containingPositionTexts, ContainingPosition.values(), ContainingPosition.BELOW_KEYWORD_PLUS_2, ContainingPosition.UNCHANGED, LocalDate.of(2026, 1, 15));
245265
final ConfigIntValue configFillPercentageToJustifyOwnColumn = new ConfigIntValue(this, "FillPercentageToJustifyOwnColumn", "Fill Ratio to justify own column", "%", 1, 20, 100);
246266
final ConfigIntValue configMaxLineLength = new ConfigIntValue(this, "MaxLineLength", "Maximum line length", "(only used to move VALUE clauses to the next line if required)", MIN_LINE_LENGTH_ABAP, HIGHER_DEFAULT_LINE_LENGTH_ABAP, 255, 200, LocalDate.of(2023, 7, 28));
247267
final ConfigBoolValue configCondenseInnerSpaces = new ConfigBoolValue(this, "CondenseInnerSpaces", "Condense inner spaces in non-aligned parts", true, true, LocalDate.of(2023, 6, 10));
248268

249-
private final ConfigValue[] configValues = new ConfigValue[] { configExecuteOnClassDefAndInterfaces, configAlignChainAction, configAlignNonChainsAction, configAlignAcrossEmptyLines, configAlignAcrossCommentLines, configAlignStructureAction, configStructureAlignStyle, configAlignEnumAction, configMaxLineLength, configFillPercentageToJustifyOwnColumn, configCondenseInnerSpaces };
269+
private final ConfigValue[] configValues = new ConfigValue[] { configExecuteOnClassDefAndInterfaces, configAlignChainAction, configAlignNonChainsAction, configAlignAcrossEmptyLines, configAlignAcrossCommentLines, configAlignStructureAction, configStructureAlignStyle, configAlignEnumAction, configContainingPosition, configMaxLineLength, configFillPercentageToJustifyOwnColumn, configCondenseInnerSpaces };
250270

251271
@Override
252272
public ConfigValue[] getConfigValues() { return configValues; }
@@ -385,6 +405,7 @@ protected void executeOn(Code code, Command startCommand, Command endCommand, bo
385405
firstLineBreaks = 1;
386406
}
387407

408+
alignGenericTypeComponents(startCommand, endCommand);
388409
alignInnerCommentLines(startCommand, endCommand);
389410
}
390411

@@ -651,20 +672,22 @@ private Token readDeclarationLine(AlignLine line, Token token, int additionalInd
651672
isTable = true;
652673

653674
// do not align table declarations with "WITH ... KEY ..." sections, because they usually should not be put on a single line;
654-
// however, do accept the short cases of "WITH EMPTY KEY" and "WITH [UNIQUE | NON-UNIQUE] DEFAULT KEY" and "WITH [UNIQUE | NON-UNIQUE] KEY comp1 [comp2 [comp3]]"
675+
// however, do accept the short cases of "WITH EMPTY KEY" and "WITH [UNIQUE | NON-UNIQUE] DEFAULT KEY" and "WITH [UNIQUE | NON-UNIQUE] KEY comp1 [comp2 [comp3]]";
676+
// also, do not align component lists of generic type definitions, i.e. TYPES ... TYPE ANY STRUCTURE CONTAINING component_list
655677
if (typeEnd.isKeyword("ASSOCIATION")
656678
|| typeEnd.isKeyword("WITH")
657679
&& !typeEnd.matchesOnSiblings(true, "WITH", "EMPTY", "KEY")
658680
&& !typeEnd.matchesOnSiblings(true, "WITH", TokenSearch.makeOptional("UNIQUE|NON-UNIQUE"), "DEFAULT", "KEY")
659-
&& !typeEnd.matchesOnSiblings(true, "WITH", TokenSearch.makeOptional("UNIQUE|NON-UNIQUE"), "KEY", TokenSearch.ANY_IDENTIFIER, ",|.")) {
681+
&& !typeEnd.matchesOnSiblings(true, "WITH", TokenSearch.makeOptional("UNIQUE|NON-UNIQUE"), "KEY", TokenSearch.ANY_IDENTIFIER, ",|.")
682+
|| typeEnd.isKeyword("CONTAINING")) {
660683

661684
// for more complex cases (with multiple components, multiple WITH key definitions, or ASSOCIATIONs)
662685
// only align up to (but excluding) "WITH" or "ASSOCIATION", thus leaving the WITH or ASSOCIATION section(s) unchanged
663-
// and keeping possible line breaks as well as manual alignment
686+
// and keeping possible line breaks as well as manual alignment; same for the component list after TYPE ANY STRUCTURE CONTAINING
664687
Term typeInfo = Term.createForTokenRange(typeStart, typeEnd.getPrev());
665688
line.setCell(Columns.TYPE.getValue(), AlignCellTerm.createSpecial(typeInfo, 0, true));
666689
return typeEnd.getLastTokenOnSiblings(true, TokenSearch.ASTERISK, ".|,");
667-
}
690+
}
668691
typeEnd = typeEnd.getNextSibling();
669692
}
670693
if (typeEnd != typeStart) { // enum members do not have a TYPE / LIKE section
@@ -806,6 +829,119 @@ private void joinColumns(AlignTable table, AlignDeclarationsAction alignAction,
806829
}
807830
}
808831

832+
/** aligns the components of TYPE ANY STRUCTURE CONTAINING ... */
833+
private void alignGenericTypeComponents(Command startCommand, Command endCommand) {
834+
// search for TYPES commands
835+
Command command = startCommand;
836+
while (command != endCommand) {
837+
if (!command.firstCodeTokenIsKeyword("TYPES")) {
838+
command = command.getNext();
839+
continue;
840+
}
841+
842+
// search for the next 'TYPE ANY STRUCTURE CONTAINING ...' within this TYPES command
843+
Token token = command.getFirstCodeToken();
844+
while (token != null) {
845+
token = token.getLastTokenOnSiblings(true, TokenSearch.ASTERISK, "TYPE", "ANY", "STRUCTURE", "CONTAINING");
846+
if (token == null)
847+
break;
848+
Token containingToken = token;
849+
Token typeToken = containingToken.getPrevCodeSibling().getPrevCodeSibling().getPrevCodeSibling();
850+
851+
// build the AlignTable ("component_name | TYPE ...") by searching for the TYPE keywords
852+
AlignTable table = new AlignTable(MAX_COMPONENT_COLUMN_COUNT);
853+
Token prevToken = token;
854+
while (token != null) {
855+
token = token.getNextCodeSibling();
856+
if (token.isCommaOrPeriod()) {
857+
finishLastLine(table, token);
858+
break;
859+
} else if (token.isKeyword("TYPE")) {
860+
finishLastLine(table, prevToken);
861+
// add a line and set the component name; the TYPE Term will be added once we know where it ends
862+
table.addLine().setCell(ComponentColumns.COMPONENT_NAME.getValue(), new AlignCellToken(prevToken));
863+
}
864+
prevToken = token;
865+
}
866+
867+
// align the table, based on the position of the first component name (keeping the position of CONTAINING unchanged)
868+
if (table.getLineCount() > 0) {
869+
Code code = table.getParentCode();
870+
871+
// move the CONTAINING keyword as configured
872+
ContainingPosition containingPosition = ContainingPosition.forValue(configContainingPosition.getValue());
873+
if (containingPosition != ContainingPosition.UNCHANGED) {
874+
int spacesLeft = 1;
875+
int lineBreaks = 0;
876+
if (containingPosition == ContainingPosition.CONTINUE || containingPosition == ContainingPosition.CONTINUE_AND_BREAK) {
877+
spacesLeft = 1;
878+
lineBreaks = 0;
879+
} else if (containingPosition == ContainingPosition.BELOW_KEYWORD_PLUS_2) {
880+
spacesLeft = command.getFirstCodeToken().getStartIndexInLine() + ABAP.INDENT_STEP;
881+
lineBreaks = 1;
882+
} else if (containingPosition == ContainingPosition.BELOW_IDENTIFIER_PLUS_2) {
883+
spacesLeft = typeToken.getPrevCodeSibling().getStartIndexInLine() + ABAP.INDENT_STEP;
884+
lineBreaks = 1;
885+
} else if (containingPosition == ContainingPosition.BELOW_TYPE) {
886+
spacesLeft = typeToken.getStartIndexInLine();
887+
lineBreaks = 1;
888+
} else if (containingPosition == ContainingPosition.BELOW_ANY_STRUCTURE) {
889+
spacesLeft = typeToken.getNextCodeSibling().getStartIndexInLine();
890+
lineBreaks = 1;
891+
}
892+
boolean changed;
893+
if (lineBreaks == 0 && containingToken.getPrev().isComment()) {
894+
spacesLeft += containingToken.getPrevCodeToken().getEndIndexInLine();
895+
changed = containingToken.setWhitespace(1, spacesLeft);
896+
} else {
897+
changed = containingToken.setWhitespace(lineBreaks, spacesLeft);
898+
}
899+
if (changed) {
900+
code.addRuleUse(this, containingToken.getParentCommand());
901+
}
902+
}
903+
904+
try {
905+
// reuse configAlignStructureAction for generic types
906+
if (getAlignStructureAction() == AlignDeclarationsAction.ALIGN_NAME_ONLY) {
907+
table.getColumn(ComponentColumns.TYPE.getValue()).joinIntoPreviousColumns(true);
908+
}
909+
// align the components either below the type identifier + 2 chars, or behind the CONTAINING keyword
910+
int basicIndent;
911+
int firstLineBreaks;
912+
if (containingPosition == ContainingPosition.CONTINUE_AND_BREAK) {
913+
basicIndent = typeToken.getPrevCodeSibling().getStartIndexInLine() + ABAP.INDENT_STEP;
914+
firstLineBreaks = 1;
915+
} else {
916+
basicIndent = containingToken.getEndIndexInLine() + 1;
917+
firstLineBreaks = 0;
918+
}
919+
Command[] changedCommands = table.align(basicIndent, firstLineBreaks, false);
920+
code.addRuleUses(this, changedCommands);
921+
} catch (UnexpectedSyntaxException e) {
922+
}
923+
}
924+
// continue searching, since TYPES: may contain multiple, comma-separated definitions of generic types
925+
}
926+
command = command.getNext();
927+
}
928+
}
929+
930+
private void finishLastLine(AlignTable table, Token endToken) {
931+
AlignLine line = table.getLastLine();
932+
if (line == null)
933+
return;
934+
Token typeToken = line.getFirstToken().getNextCodeSibling();
935+
Token lastToken = endToken.getPrevCodeSibling();
936+
try {
937+
Term typeTerm = Term.createForTokenRange(typeToken, lastToken);
938+
line.setCell(ComponentColumns.TYPE.getValue(), new AlignCellTerm(typeTerm));
939+
} catch (UnexpectedSyntaxException e) {
940+
table.removeLastLine();
941+
return;
942+
}
943+
}
944+
809945
private void alignInnerCommentLines(Command startCommand, Command endCommand) {
810946
Command command = startCommand;
811947
int blockLevel = 0;
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.sap.adt.abapcleaner.rules.alignment;
2+
3+
public enum ContainingPosition {
4+
UNCHANGED,
5+
CONTINUE,
6+
CONTINUE_AND_BREAK,
7+
BELOW_KEYWORD_PLUS_2,
8+
BELOW_IDENTIFIER_PLUS_2,
9+
BELOW_TYPE,
10+
BELOW_ANY_STRUCTURE;
11+
12+
public static final int SIZE = java.lang.Integer.SIZE;
13+
14+
public int getValue() {
15+
return this.ordinal();
16+
}
17+
18+
public static ContainingPosition forValue(int value) {
19+
return values()[value];
20+
}
21+
}

test/com.sap.adt.abapcleaner.test/src/com/sap/adt/abapcleaner/rulebase/RuleTestBase.java

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -476,7 +476,7 @@ private boolean runStressTest(StressTestType stressTestType, int insertAfterToke
476476
return false;
477477
}
478478
} catch (IntegrityBrokenException ex) {
479-
fail("Error inserting stress test tokens:" + ex.getMessage() + stressTestInfo );
479+
fail("Error inserting stress test tokens: " + getStressTestFailMessage(ex, stressTestInfo, code));
480480
}
481481
@SuppressWarnings("unused")
482482
String codeBeforeCleanup = code.toString(); // for debugging
@@ -485,8 +485,9 @@ private boolean runStressTest(StressTestType stressTestType, int insertAfterToke
485485
// but in any case, the result must keep referential integrity
486486
try {
487487
getRule().executeIfAllowedOn(code, releaseRestrictionFromUI);
488-
} catch (UnexpectedSyntaxBeforeChanges | UnexpectedSyntaxAfterChanges e) {
489-
fail(e.getMessage() + stressTestInfo);
488+
} catch (UnexpectedSyntaxBeforeChanges | UnexpectedSyntaxAfterChanges ex) {
489+
fail("Unexpected syntax executing rule '" + getRule().getDisplayName() + "': "
490+
+ getStressTestFailMessage(ex, stressTestInfo, code));
490491
}
491492

492493
// test the referential integrity of the resulting objects (Code, Command, Token etc.)
@@ -495,12 +496,18 @@ private boolean runStressTest(StressTestType stressTestType, int insertAfterToke
495496
if (stressTestType != StressTestType.COLON) {
496497
code.checkSyntax(true);
497498
}
498-
} catch (IntegrityBrokenException e1) {
499-
fail("Error after executing rule '" + getRule().getDisplayName() + "': " + e1.getMessage() + stressTestInfo);
499+
} catch (IntegrityBrokenException ex) {
500+
fail("Error after executing rule '" + getRule().getDisplayName() + "': "
501+
+ getStressTestFailMessage(ex, stressTestInfo, code));
500502
}
501503
return true;
502504
}
503505

506+
private String getStressTestFailMessage(Throwable ex, String stressTestInfo, Code code) {
507+
return ex.getMessage() + System.lineSeparator() + stressTestInfo + " resulted in:"
508+
+ System.lineSeparator() + code.toString();
509+
}
510+
504511
private void testDiffNavigator(Code code, DiffDoc diffDoc) {
505512
DiffNavigator diffNav = DiffNavigator.create();
506513
diffNav.refreshCode(code, diffDoc, 0, 0, 0);

0 commit comments

Comments
 (0)