From 4bd5ca1c4e559996f06962f08584ed32054de9f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Dinis=20Ferreira?= Date: Sat, 30 May 2026 11:40:06 +0200 Subject: [PATCH 1/4] fix: validate and quick-fix duplicate languages in CHECKCFG The CHECKCFG grammar accepts multiple `for { ... }` blocks in one configuration. Nothing validated against the same language appearing twice, so duplicate blocks were silently allowed with no defined semantics for how their configurations combine. Add an error-severity validation plus a quick-fix: - checkConfiguredLanguageUnique on CheckConfiguration reports an error on each duplicate ConfiguredLanguageValidator sharing a language name. Mirrors the existing catalog/check/parameter uniqueness validators and reuses the getDuplicates() helper. Simpler than the catalog case: getLanguage() returns a String, so no proxy check is needed. - removeDuplicateLanguageConfiguration quick-fix merges every later block's parameter and catalog configurations into the first occurrence and deletes the duplicates. Tests live in CheckCfgTest; the surrounding checkcfg.core.test validation files are migrated Xtend -> Java to add them. This revives the approach from the long-closed #104 (validation + merge quick-fix), reimplemented against the current Java validator/quick-fix code, which was renamed and migrated off Xtend since 2018. Closes #103 Co-Authored-By: Claude Opus 4.7 --- .../ddk/checkcfg/validation/CheckCfgTest.java | 17 ++++++++ .../validation/CheckCfgValidator.java | 30 +++++++++++++ .../ddk/checkcfg/validation/IssueCodes.java | 1 + .../ddk/checkcfg/validation/Messages.java | 1 + .../checkcfg/validation/messages.properties | 1 + .../ui/quickfix/CheckCfgQuickfixProvider.java | 43 +++++++++++++++++++ .../ddk/checkcfg/ui/quickfix/Messages.java | 2 + .../checkcfg/ui/quickfix/messages.properties | 2 + 8 files changed, 97 insertions(+) diff --git a/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/validation/CheckCfgTest.java b/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/validation/CheckCfgTest.java index dbad41e80..96c982960 100644 --- a/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/validation/CheckCfgTest.java +++ b/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/validation/CheckCfgTest.java @@ -58,4 +58,21 @@ public void testUnknownLanguageNotOk() throws Exception { helper.assertError(model, CheckcfgPackage.Literals.CONFIGURED_LANGUAGE_VALIDATOR, IssueCodes.UNKNOWN_LANGUAGE); } + @Test + public void testDuplicateLanguageNotOk() throws Exception { + final CheckConfiguration model = parser.parse(""" + check configuration Test + + for com.avaloq.tools.ddk.^check.TestLanguage { + + } + + for com.avaloq.tools.ddk.^check.TestLanguage { + + } + + """); + helper.assertError(model, CheckcfgPackage.Literals.CONFIGURED_LANGUAGE_VALIDATOR, IssueCodes.DUPLICATE_LANGUAGE_CONFIGURATION); + } + } diff --git a/com.avaloq.tools.ddk.checkcfg.core/src/com/avaloq/tools/ddk/checkcfg/validation/CheckCfgValidator.java b/com.avaloq.tools.ddk.checkcfg.core/src/com/avaloq/tools/ddk/checkcfg/validation/CheckCfgValidator.java index 61a1b61f3..f65d12c8f 100644 --- a/com.avaloq.tools.ddk.checkcfg.core/src/com/avaloq/tools/ddk/checkcfg/validation/CheckCfgValidator.java +++ b/com.avaloq.tools.ddk.checkcfg.core/src/com/avaloq/tools/ddk/checkcfg/validation/CheckCfgValidator.java @@ -313,6 +313,36 @@ public void checkConfiguredLanguageExists(final ConfiguredLanguageValidator vali } } + /** + * Checks that within a Check Configuration all Configured Language Validators are unique, meaning that + * the same language can only be configured in one place. + * + * @param configuration + * the configuration + */ + @Check + public void checkConfiguredLanguageUnique(final CheckConfiguration configuration) { + if (configuration.getLanguageValidatorConfigurations().size() < 2) { + return; + } + Predicate predicate = new Predicate() { + @Override + public boolean apply(final ConfiguredLanguageValidator validator) { + return validator.getLanguage() != null; + } + }; + Function function = new Function() { + @Override + public String apply(final ConfiguredLanguageValidator from) { + return from.getLanguage(); + } + }; + for (final ConfiguredLanguageValidator v : getDuplicates(predicate, function, configuration.getLanguageValidatorConfigurations())) { + error(Messages.CheckCfgJavaValidator_DUPLICATE_LANGUAGE_CONFIGURATION, v, CheckcfgPackage.Literals.CONFIGURED_LANGUAGE_VALIDATOR__LANGUAGE, ValidationMessageAcceptor.INSIGNIFICANT_INDEX, IssueCodes.DUPLICATE_LANGUAGE_CONFIGURATION); + } + + } + /** * Checks that a Configured Check has unique Configured Parameters. * diff --git a/com.avaloq.tools.ddk.checkcfg.core/src/com/avaloq/tools/ddk/checkcfg/validation/IssueCodes.java b/com.avaloq.tools.ddk.checkcfg.core/src/com/avaloq/tools/ddk/checkcfg/validation/IssueCodes.java index 2a9818854..75ae4c479 100644 --- a/com.avaloq.tools.ddk.checkcfg.core/src/com/avaloq/tools/ddk/checkcfg/validation/IssueCodes.java +++ b/com.avaloq.tools.ddk.checkcfg.core/src/com/avaloq/tools/ddk/checkcfg/validation/IssueCodes.java @@ -26,6 +26,7 @@ public final class IssueCodes { public static final String DUPLICATE_CATALOG_CONFIGURATION = ISSUE_CODE_PREFIX + "duplicate_catalog_configuration"; public static final String DUPLICATE_CHECK_CONFIGURATION = ISSUE_CODE_PREFIX + "duplicate_check_configuration"; public static final String UNKNOWN_LANGUAGE = ISSUE_CODE_PREFIX + "unknown_language"; + public static final String DUPLICATE_LANGUAGE_CONFIGURATION = ISSUE_CODE_PREFIX + "duplicate_language_configuration"; public static final String DUPLICATE_PARAMETER_CONFIGURATION = ISSUE_CODE_PREFIX + "duplicate_parameter_configuration"; public static final String SEVERITY_NOT_ALLOWED = ISSUE_CODE_PREFIX + "severity_not_allowed"; public static final String PARAMETER_VALUE_NOT_ALLOWED = ISSUE_CODE_PREFIX + "parameter_value_not_allowed"; diff --git a/com.avaloq.tools.ddk.checkcfg.core/src/com/avaloq/tools/ddk/checkcfg/validation/Messages.java b/com.avaloq.tools.ddk.checkcfg.core/src/com/avaloq/tools/ddk/checkcfg/validation/Messages.java index e96bcdc20..66dfb4bb3 100644 --- a/com.avaloq.tools.ddk.checkcfg.core/src/com/avaloq/tools/ddk/checkcfg/validation/Messages.java +++ b/com.avaloq.tools.ddk.checkcfg.core/src/com/avaloq/tools/ddk/checkcfg/validation/Messages.java @@ -25,6 +25,7 @@ public class Messages extends NLS { public static String CheckCfgJavaValidator_CONFIGURED_PARAM_EQUALS_DEFAULT; public static String CheckCfgJavaValidator_DUPLICATE_CATALOG_CONFIGURATION; public static String CheckCfgJavaValidator_DUPLICATE_CHECK_CONFIGURATION; + public static String CheckCfgJavaValidator_DUPLICATE_LANGUAGE_CONFIGURATION; public static String CheckCfgJavaValidator_DUPLICATE_PARAMETER_CONFIGURATION; public static String CheckCfgJavaValidator_SEVERITY_NOT_ALLOWED; diff --git a/com.avaloq.tools.ddk.checkcfg.core/src/com/avaloq/tools/ddk/checkcfg/validation/messages.properties b/com.avaloq.tools.ddk.checkcfg.core/src/com/avaloq/tools/ddk/checkcfg/validation/messages.properties index 63001722a..1ad5b893d 100644 --- a/com.avaloq.tools.ddk.checkcfg.core/src/com/avaloq/tools/ddk/checkcfg/validation/messages.properties +++ b/com.avaloq.tools.ddk.checkcfg.core/src/com/avaloq/tools/ddk/checkcfg/validation/messages.properties @@ -5,5 +5,6 @@ CheckCfgJavaValidator_FINAL_CHECK_NOT_CONFIGURABLE=Final checks may not be confi CheckCfgJavaValidator_CONFIGURED_PARAM_EQUALS_DEFAULT=Configured value for ''{0}'' equals default CheckCfgJavaValidator_DUPLICATE_CATALOG_CONFIGURATION=Duplicate catalog configuration CheckCfgJavaValidator_DUPLICATE_CHECK_CONFIGURATION=Duplicate check configuration +CheckCfgJavaValidator_DUPLICATE_LANGUAGE_CONFIGURATION=Duplicate language configuration CheckCfgJavaValidator_DUPLICATE_PARAMETER_CONFIGURATION=Duplicate parameter configuration CheckCfgJavaValidator_SEVERITY_NOT_ALLOWED=Configured severity is not allowed \ No newline at end of file diff --git a/com.avaloq.tools.ddk.checkcfg.ui/src/com/avaloq/tools/ddk/checkcfg/ui/quickfix/CheckCfgQuickfixProvider.java b/com.avaloq.tools.ddk.checkcfg.ui/src/com/avaloq/tools/ddk/checkcfg/ui/quickfix/CheckCfgQuickfixProvider.java index 81495357b..244eebf23 100644 --- a/com.avaloq.tools.ddk.checkcfg.ui/src/com/avaloq/tools/ddk/checkcfg/ui/quickfix/CheckCfgQuickfixProvider.java +++ b/com.avaloq.tools.ddk.checkcfg.ui/src/com/avaloq/tools/ddk/checkcfg/ui/quickfix/CheckCfgQuickfixProvider.java @@ -10,6 +10,9 @@ *******************************************************************************/ package com.avaloq.tools.ddk.checkcfg.ui.quickfix; +import java.util.ArrayList; +import java.util.List; + import org.eclipse.emf.ecore.EObject; import org.eclipse.jface.text.BadLocationException; import org.eclipse.osgi.util.NLS; @@ -26,6 +29,7 @@ import com.avaloq.tools.ddk.checkcfg.checkcfg.CheckConfiguration; import com.avaloq.tools.ddk.checkcfg.checkcfg.ConfiguredCatalog; import com.avaloq.tools.ddk.checkcfg.checkcfg.ConfiguredCheck; +import com.avaloq.tools.ddk.checkcfg.checkcfg.ConfiguredLanguageValidator; import com.avaloq.tools.ddk.checkcfg.checkcfg.SeverityKind; import com.avaloq.tools.ddk.checkcfg.validation.IssueCodes; @@ -158,4 +162,43 @@ public void apply(final EObject element, final IModificationContext context) { } }); } + + /** + * Removes duplicate language configurations by merging the contents of every later occurrence into the first one + * and deleting the duplicates. + * + * @param issue + * the issue + * @param acceptor + * the acceptor + */ + @Fix(IssueCodes.DUPLICATE_LANGUAGE_CONFIGURATION) + public void removeDuplicateLanguageConfiguration(final Issue issue, final IssueResolutionAcceptor acceptor) { + acceptor.accept(issue, Messages.CheckCfgQuickfixProvider_REMOVE_DUPLICATE_LANG_LABEL, Messages.CheckCfgQuickfixProvider_REMOVE_DUPLICATE_LANG_DESCN, null, new ISemanticModification() { + @Override + public void apply(final EObject element, final IModificationContext context) { + final CheckConfiguration configuration = EcoreUtil2.getContainerOfType(element, CheckConfiguration.class); + final String languageName = ((ConfiguredLanguageValidator) element).getLanguage(); + if (configuration == null || languageName == null) { + return; + } + ConfiguredLanguageValidator first = null; + final List duplicates = new ArrayList(); + for (final ConfiguredLanguageValidator validator : configuration.getLanguageValidatorConfigurations()) { + if (languageName.equals(validator.getLanguage())) { + if (first == null) { + first = validator; + } else { + duplicates.add(validator); + } + } + } + for (final ConfiguredLanguageValidator duplicate : duplicates) { + first.getParameterConfigurations().addAll(duplicate.getParameterConfigurations()); + first.getCatalogConfigurations().addAll(duplicate.getCatalogConfigurations()); + configuration.getLanguageValidatorConfigurations().remove(duplicate); + } + } + }); + } } diff --git a/com.avaloq.tools.ddk.checkcfg.ui/src/com/avaloq/tools/ddk/checkcfg/ui/quickfix/Messages.java b/com.avaloq.tools.ddk.checkcfg.ui/src/com/avaloq/tools/ddk/checkcfg/ui/quickfix/Messages.java index 22dfc2036..3cb496a88 100644 --- a/com.avaloq.tools.ddk.checkcfg.ui/src/com/avaloq/tools/ddk/checkcfg/ui/quickfix/Messages.java +++ b/com.avaloq.tools.ddk.checkcfg.ui/src/com/avaloq/tools/ddk/checkcfg/ui/quickfix/Messages.java @@ -28,6 +28,8 @@ public class Messages extends NLS { public static String CheckCfgQuickfixProvider_REMOVE_DUPLICATE_CATALOG_LABEL; public static String CheckCfgQuickfixProvider_REMOVE_DUPLICATE_CHECK_DESCN; public static String CheckCfgQuickfixProvider_REMOVE_DUPLICATE_CHECK_LABEL; + public static String CheckCfgQuickfixProvider_REMOVE_DUPLICATE_LANG_DESCN; + public static String CheckCfgQuickfixProvider_REMOVE_DUPLICATE_LANG_LABEL; public static String CheckCfgQuickfixProvider_REMOVE_DUPLICATE_PARAM_DESCN; public static String CheckCfgQuickfixProvider_REMOVE_DUPLICATE_PARAM_LABEL; diff --git a/com.avaloq.tools.ddk.checkcfg.ui/src/com/avaloq/tools/ddk/checkcfg/ui/quickfix/messages.properties b/com.avaloq.tools.ddk.checkcfg.ui/src/com/avaloq/tools/ddk/checkcfg/ui/quickfix/messages.properties index 539316d64..33b296a44 100644 --- a/com.avaloq.tools.ddk.checkcfg.ui/src/com/avaloq/tools/ddk/checkcfg/ui/quickfix/messages.properties +++ b/com.avaloq.tools.ddk.checkcfg.ui/src/com/avaloq/tools/ddk/checkcfg/ui/quickfix/messages.properties @@ -8,5 +8,7 @@ CheckCfgQuickfixProvider_REMOVE_DUPLICATE_CATALOG_DESCN=Remove the duplicate cat CheckCfgQuickfixProvider_REMOVE_DUPLICATE_CATALOG_LABEL=Remove duplicate catalog CheckCfgQuickfixProvider_REMOVE_DUPLICATE_CHECK_DESCN=Remove the duplicate check configuration. CheckCfgQuickfixProvider_REMOVE_DUPLICATE_CHECK_LABEL=Remove duplicate check +CheckCfgQuickfixProvider_REMOVE_DUPLICATE_LANG_DESCN=Merge the duplicate language configurations into the first occurrence and remove the duplicates. +CheckCfgQuickfixProvider_REMOVE_DUPLICATE_LANG_LABEL=Merge duplicate language configurations CheckCfgQuickfixProvider_REMOVE_DUPLICATE_PARAM_DESCN=Remove the duplicate parameter configuration. CheckCfgQuickfixProvider_REMOVE_DUPLICATE_PARAM_LABEL=Remove duplicate parameter \ No newline at end of file From 01559a719d591a8d7e492a03c23963ee16443077 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Dinis=20Ferreira?= Date: Sun, 14 Jun 2026 00:14:40 +0200 Subject: [PATCH 2/4] test: cover the duplicate-language merge as a UI-independent core operation Extracts the merge logic from the UI quickfix provider's anonymous ISemanticModification into CheckCfgQuickfixes in checkcfg.core, leaving the provider a thin @Fix wrapper. The model transformation is now exercisable without an editor or workbench, and CheckCfgQuickfixesTest covers six scenarios (catalog merge, parameter merge, 3+ occurrences, language selectivity, invocation-independence, empty duplicate) against parsed models in checkcfg.core.test. Co-Authored-By: Claude Fable 5 --- .../quickfix/CheckCfgQuickfixesTest.java | 135 ++++++++++++++++++ .../ddk/checkcfg/test/CheckCfgTestSuite.java | 2 + .../META-INF/MANIFEST.MF | 1 + .../checkcfg/quickfix/CheckCfgQuickfixes.java | 67 +++++++++ .../ui/quickfix/CheckCfgQuickfixProvider.java | 26 +--- 5 files changed, 207 insertions(+), 24 deletions(-) create mode 100644 com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/quickfix/CheckCfgQuickfixesTest.java create mode 100644 com.avaloq.tools.ddk.checkcfg.core/src/com/avaloq/tools/ddk/checkcfg/quickfix/CheckCfgQuickfixes.java diff --git a/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/quickfix/CheckCfgQuickfixesTest.java b/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/quickfix/CheckCfgQuickfixesTest.java new file mode 100644 index 000000000..64edb9e63 --- /dev/null +++ b/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/quickfix/CheckCfgQuickfixesTest.java @@ -0,0 +1,135 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ +package com.avaloq.tools.ddk.checkcfg.quickfix; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; + +import org.eclipse.xtext.testing.InjectWith; +import org.eclipse.xtext.testing.extensions.InjectionExtension; +import org.eclipse.xtext.testing.util.ParseHelper; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import com.avaloq.tools.ddk.checkcfg.CheckCfgUiInjectorProvider; +import com.avaloq.tools.ddk.checkcfg.checkcfg.CheckConfiguration; +import com.avaloq.tools.ddk.checkcfg.checkcfg.ConfiguredLanguageValidator; +import com.google.inject.Inject; + +/** + * Tests the UI-independent model transformation behind the duplicate-language-configuration quickfix + * ({@link CheckCfgQuickfixes#mergeDuplicateLanguageConfigurations(ConfiguredLanguageValidator)}). + */ +@InjectWith(CheckCfgUiInjectorProvider.class) +@ExtendWith(InjectionExtension.class) +@SuppressWarnings("nls") +public class CheckCfgQuickfixesTest { + + private static final String LANG = "com.avaloq.tools.ddk.^check.TestLanguage"; + private static final String OTHER_LANG = "org.example.OtherLanguage"; + private static final String DUPLICATES_REMOVED = "duplicates removed"; + + @Inject + private ParseHelper parser; + + /** Builds a {@code for { }} validator block. */ + private static String forBlock(final String language, final String body) { + return " for " + language + " {\n " + body + "\n }\n"; + } + + /** Assembles the given validator blocks into a single check configuration source. */ + private static String source(final String... blocks) { + return "check configuration Test\n" + String.join("", blocks); + } + + private static List validators(final CheckConfiguration model) { + return model.getLanguageValidatorConfigurations(); + } + + /** Two occurrences of the same language, each with a distinct catalog, merge into one validator holding both catalogs. */ + @Test + public void testMergesDistinctCatalogs() throws Exception { + final CheckConfiguration model = parser.parse(source( + forBlock(LANG, "catalog a.CatA { default CheckA }"), + forBlock(LANG, "catalog b.CatB { default CheckB }"))); + CheckCfgQuickfixes.mergeDuplicateLanguageConfigurations(validators(model).get(0)); + assertEquals(1, validators(model).size(), DUPLICATES_REMOVED); + assertEquals(2, validators(model).get(0).getCatalogConfigurations().size(), "both catalogs merged into the survivor"); + } + + /** The inherited parameter-configuration list is merged too, not just catalogs. */ + @Test + public void testMergesParameterConfigurations() throws Exception { + final CheckConfiguration model = parser.parse(source( + forBlock(LANG, "integrationRelevant = true"), + forBlock(LANG, "nameOverrides = #['x']"))); + CheckCfgQuickfixes.mergeDuplicateLanguageConfigurations(validators(model).get(0)); + assertEquals(1, validators(model).size(), DUPLICATES_REMOVED); + assertEquals(2, validators(model).get(0).getParameterConfigurations().size(), "both parameters merged into the survivor"); + } + + /** More than two occurrences all collapse into the first. */ + @Test + public void testMergesThreeOccurrences() throws Exception { + final CheckConfiguration model = parser.parse(source( + forBlock(LANG, "catalog a.CatA { default CheckA }"), + forBlock(LANG, "catalog b.CatB { default CheckB }"), + forBlock(LANG, "catalog c.CatC { default CheckC }"))); + CheckCfgQuickfixes.mergeDuplicateLanguageConfigurations(validators(model).get(0)); + assertEquals(1, validators(model).size(), "all duplicates removed"); + assertEquals(3, validators(model).get(0).getCatalogConfigurations().size(), "all three catalogs merged into the survivor"); + } + + /** Only the targeted language is merged; an unrelated duplicated language is left untouched. */ + @Test + public void testOnlyTargetLanguageMerged() throws Exception { + final CheckConfiguration model = parser.parse(source( + forBlock(LANG, "catalog a.CatA { default CheckA }"), + forBlock(OTHER_LANG, "catalog b.CatB { default CheckB }"), + forBlock(LANG, "catalog c.CatC { default CheckC }"), + forBlock(OTHER_LANG, "catalog d.CatD { default CheckD }"))); + CheckCfgQuickfixes.mergeDuplicateLanguageConfigurations(validators(model).get(0)); + assertEquals(3, validators(model).size(), "only the two occurrences of the target language collapse to one"); + assertEquals(2, validators(model).stream().filter(v -> OTHER_LANG.equals(v.getLanguage())).count(), "the other duplicated language is untouched"); + } + + /** The result is independent of which duplicate occurrence the fix is invoked from. */ + @Test + public void testIndependentOfInvokedOccurrence() throws Exception { + final CheckConfiguration fromFirst = parser.parse(source( + forBlock(LANG, "catalog a.CatA { default CheckA }"), + forBlock(LANG, "catalog b.CatB { default CheckB }"))); + CheckCfgQuickfixes.mergeDuplicateLanguageConfigurations(validators(fromFirst).get(0)); + + final CheckConfiguration fromLast = parser.parse(source( + forBlock(LANG, "catalog a.CatA { default CheckA }"), + forBlock(LANG, "catalog b.CatB { default CheckB }"))); + CheckCfgQuickfixes.mergeDuplicateLanguageConfigurations(validators(fromLast).get(1)); + + assertEquals(1, validators(fromFirst).size(), "invoking from the first occurrence merges"); + assertEquals(1, validators(fromLast).size(), "invoking from the last occurrence merges identically"); + assertEquals(validators(fromFirst).get(0).getCatalogConfigurations().size(), + validators(fromLast).get(0).getCatalogConfigurations().size(), "same merged content either way"); + } + + /** An empty duplicate is simply removed; no crash, survivor unchanged. */ + @Test + public void testEmptyDuplicateRemoved() throws Exception { + final CheckConfiguration model = parser.parse(source( + forBlock(LANG, "catalog a.CatA { default CheckA }"), + forBlock(LANG, ""))); + CheckCfgQuickfixes.mergeDuplicateLanguageConfigurations(validators(model).get(0)); + assertEquals(1, validators(model).size(), "empty duplicate removed"); + assertEquals(1, validators(model).get(0).getCatalogConfigurations().size(), "survivor's catalog preserved"); + } + +} diff --git a/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/test/CheckCfgTestSuite.java b/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/test/CheckCfgTestSuite.java index 155b8855e..c4360b03c 100644 --- a/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/test/CheckCfgTestSuite.java +++ b/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/test/CheckCfgTestSuite.java @@ -14,6 +14,7 @@ import org.junit.platform.suite.api.Suite; import com.avaloq.tools.ddk.checkcfg.contentassist.CheckCfgContentAssistTest; +import com.avaloq.tools.ddk.checkcfg.quickfix.CheckCfgQuickfixesTest; import com.avaloq.tools.ddk.checkcfg.scoping.CheckCfgScopeProviderTest; import com.avaloq.tools.ddk.checkcfg.syntax.CheckCfgSyntaxTest; import com.avaloq.tools.ddk.checkcfg.validation.CheckCfgConfiguredParameterValidationsTest; @@ -29,6 +30,7 @@ // @Format-Off CheckCfgConfiguredParameterValidationsTest.class, CheckCfgContentAssistTest.class, + CheckCfgQuickfixesTest.class, CheckCfgScopeProviderTest.class, CheckCfgSyntaxTest.class, CheckCfgTest.class, diff --git a/com.avaloq.tools.ddk.checkcfg.core/META-INF/MANIFEST.MF b/com.avaloq.tools.ddk.checkcfg.core/META-INF/MANIFEST.MF index 484177c1b..982fd8ecd 100644 --- a/com.avaloq.tools.ddk.checkcfg.core/META-INF/MANIFEST.MF +++ b/com.avaloq.tools.ddk.checkcfg.core/META-INF/MANIFEST.MF @@ -32,6 +32,7 @@ Export-Package: com.avaloq.tools.ddk.checkcfg, com.avaloq.tools.ddk.checkcfg.jvmmodel, com.avaloq.tools.ddk.checkcfg.parser.antlr, com.avaloq.tools.ddk.checkcfg.parser.antlr.internal, + com.avaloq.tools.ddk.checkcfg.quickfix, com.avaloq.tools.ddk.checkcfg.services, com.avaloq.tools.ddk.checkcfg.validation, com.avaloq.tools.ddk.checkcfg.serializer diff --git a/com.avaloq.tools.ddk.checkcfg.core/src/com/avaloq/tools/ddk/checkcfg/quickfix/CheckCfgQuickfixes.java b/com.avaloq.tools.ddk.checkcfg.core/src/com/avaloq/tools/ddk/checkcfg/quickfix/CheckCfgQuickfixes.java new file mode 100644 index 000000000..b7bacd4ff --- /dev/null +++ b/com.avaloq.tools.ddk.checkcfg.core/src/com/avaloq/tools/ddk/checkcfg/quickfix/CheckCfgQuickfixes.java @@ -0,0 +1,67 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ +package com.avaloq.tools.ddk.checkcfg.quickfix; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.xtext.EcoreUtil2; + +import com.avaloq.tools.ddk.checkcfg.checkcfg.CheckConfiguration; +import com.avaloq.tools.ddk.checkcfg.checkcfg.ConfiguredLanguageValidator; + +/** + * Model transformations backing the Check Configuration quickfixes. + *

+ * These operate purely on the EMF model and carry no UI dependency, so they can be unit-tested without an editor or + * workbench. The UI quickfix provider is a thin wrapper that invokes them from an {@code ISemanticModification}. + *

+ */ +public final class CheckCfgQuickfixes { + + private CheckCfgQuickfixes() { + // utility class + } + + /** + * Removes duplicate language configurations from the {@link CheckConfiguration} containing the given validator, by + * merging the contents (catalog and parameter configurations) of every later occurrence of the same language into the + * first occurrence and deleting the duplicates. + * + * @param element + * a configured language validator flagged as a duplicate; the merge target is derived from its containing + * configuration and its language name, so the result is independent of which duplicate occurrence is passed + */ + public static void mergeDuplicateLanguageConfigurations(final ConfiguredLanguageValidator element) { + final CheckConfiguration configuration = EcoreUtil2.getContainerOfType(element, CheckConfiguration.class); + final String languageName = element.getLanguage(); + if (configuration == null || languageName == null) { + return; + } + ConfiguredLanguageValidator first = null; + final List duplicates = new ArrayList(); + for (final ConfiguredLanguageValidator validator : configuration.getLanguageValidatorConfigurations()) { + if (languageName.equals(validator.getLanguage())) { + if (first == null) { + first = validator; + } else { + duplicates.add(validator); + } + } + } + for (final ConfiguredLanguageValidator duplicate : duplicates) { + first.getParameterConfigurations().addAll(duplicate.getParameterConfigurations()); + first.getCatalogConfigurations().addAll(duplicate.getCatalogConfigurations()); + configuration.getLanguageValidatorConfigurations().remove(duplicate); + } + } + +} diff --git a/com.avaloq.tools.ddk.checkcfg.ui/src/com/avaloq/tools/ddk/checkcfg/ui/quickfix/CheckCfgQuickfixProvider.java b/com.avaloq.tools.ddk.checkcfg.ui/src/com/avaloq/tools/ddk/checkcfg/ui/quickfix/CheckCfgQuickfixProvider.java index 244eebf23..31b824800 100644 --- a/com.avaloq.tools.ddk.checkcfg.ui/src/com/avaloq/tools/ddk/checkcfg/ui/quickfix/CheckCfgQuickfixProvider.java +++ b/com.avaloq.tools.ddk.checkcfg.ui/src/com/avaloq/tools/ddk/checkcfg/ui/quickfix/CheckCfgQuickfixProvider.java @@ -10,9 +10,6 @@ *******************************************************************************/ package com.avaloq.tools.ddk.checkcfg.ui.quickfix; -import java.util.ArrayList; -import java.util.List; - import org.eclipse.emf.ecore.EObject; import org.eclipse.jface.text.BadLocationException; import org.eclipse.osgi.util.NLS; @@ -31,6 +28,7 @@ import com.avaloq.tools.ddk.checkcfg.checkcfg.ConfiguredCheck; import com.avaloq.tools.ddk.checkcfg.checkcfg.ConfiguredLanguageValidator; import com.avaloq.tools.ddk.checkcfg.checkcfg.SeverityKind; +import com.avaloq.tools.ddk.checkcfg.quickfix.CheckCfgQuickfixes; import com.avaloq.tools.ddk.checkcfg.validation.IssueCodes; @@ -177,27 +175,7 @@ public void removeDuplicateLanguageConfiguration(final Issue issue, final IssueR acceptor.accept(issue, Messages.CheckCfgQuickfixProvider_REMOVE_DUPLICATE_LANG_LABEL, Messages.CheckCfgQuickfixProvider_REMOVE_DUPLICATE_LANG_DESCN, null, new ISemanticModification() { @Override public void apply(final EObject element, final IModificationContext context) { - final CheckConfiguration configuration = EcoreUtil2.getContainerOfType(element, CheckConfiguration.class); - final String languageName = ((ConfiguredLanguageValidator) element).getLanguage(); - if (configuration == null || languageName == null) { - return; - } - ConfiguredLanguageValidator first = null; - final List duplicates = new ArrayList(); - for (final ConfiguredLanguageValidator validator : configuration.getLanguageValidatorConfigurations()) { - if (languageName.equals(validator.getLanguage())) { - if (first == null) { - first = validator; - } else { - duplicates.add(validator); - } - } - } - for (final ConfiguredLanguageValidator duplicate : duplicates) { - first.getParameterConfigurations().addAll(duplicate.getParameterConfigurations()); - first.getCatalogConfigurations().addAll(duplicate.getCatalogConfigurations()); - configuration.getLanguageValidatorConfigurations().remove(duplicate); - } + CheckCfgQuickfixes.mergeDuplicateLanguageConfigurations((ConfiguredLanguageValidator) element); } }); } From 61b5a11439aa55ad73c23814abb4435677a434eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Dinis=20Ferreira?= Date: Sun, 14 Jun 2026 11:39:39 +0200 Subject: [PATCH 3/4] docs: document the order-preserving merge contract (response to review) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To the reviewer: Thank you — your three findings on the dedup-aware merge were all correct, and this commit is the considered response. The short version: I implemented the deduplication direction your findings pointed at, it made things worse, so I reverted it and kept the original order-preserving concatenation, now with its contract and limitations documented explicitly. Reasoning in full: Findings 1 and 2 (same-catalog merge dropped catalog-level parameters; duplicate-check merge dropped the earlier check's parameters). You were right and these were genuine data loss. Crucially they existed ONLY in the dedup-aware merge I had added, not in the original concatenation: ConfiguredCatalog and ConfiguredCheck both extend ConfigurableSection, so each carries its own parameter configurations, and collapsing same-named catalogs/checks silently dropped them. I have reverted that dedup commit in full. With no deduplication nothing is dropped, so findings 1 and 2 no longer apply to the code under review. Why concatenation rather than a corrected, deeper dedup. The properties generator is last-wins (it puts configurations into a Properties map in order, so a later definition overrides an earlier one). Order-preserving concatenation relocates every entry into one block in its original order, which reproduces the exact generated output of the original duplicate blocks — it is behaviour- preserving. Your findings demonstrated the opposite for dedup: any scheme that removes the resulting language-scoped duplicates ends up dropping scoped parameters the generator would have emitted. A fully faithful dedup would have to replicate the generator's whole inheritance resolution inside the quick-fix, which is disproportionate and a large new bug surface. So the safe choice is to move entries without rewriting them. Finding 3 (language-level parameter rescoping). Agreed, and note it is inherent to collapsing two blocks into one — it is present in any merge, including the original, not something dedup introduced. In the common case it is harmless; in the rare case where the duplicate blocks carry different block-level inherited parameters it genuinely cannot be represented in a single block. We accept this: the quick-fix resolves the duplicate-LANGUAGE error the issue asks for; it does not claim to resolve every downstream inheritance conflict, whose correct resolution depends on user intent the tool cannot infer. Residual unvalidated duplicates (your earlier rounds). The concatenation can leave language-scoped duplicate catalogs/parameters that no validator flags. That validation gap is pre-existing and independent of this change: language- scoped catalog/parameter uniqueness is simply not validated anywhere today, and the originating issue (#103) asks only for duplicate-language validation plus a merge quick-fix, both of which this PR delivers. Adding language-scoped uniqueness validation is worthwhile but is a separate concern, tracked as a follow-up rather than expanding this PR. Until then the residual duplicates are benign at generation (same last-wins outcome). Coverage gap (no @Fix-execution test). Accepted. As you verified, the provider cast is safe by construction — the error is reported on a ConfiguredLanguageValidator source — and the model transformation itself is covered by the core tests. This commit changes only the Javadoc of mergeDuplicateLanguageConfigurations to record the order-preserving contract, the behaviour-preservation rationale, and the documented scope boundary, so the intent is explicit for future readers. Co-Authored-By: Claude Fable 5 --- .../checkcfg/quickfix/CheckCfgQuickfixes.java | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/com.avaloq.tools.ddk.checkcfg.core/src/com/avaloq/tools/ddk/checkcfg/quickfix/CheckCfgQuickfixes.java b/com.avaloq.tools.ddk.checkcfg.core/src/com/avaloq/tools/ddk/checkcfg/quickfix/CheckCfgQuickfixes.java index b7bacd4ff..186f4d446 100644 --- a/com.avaloq.tools.ddk.checkcfg.core/src/com/avaloq/tools/ddk/checkcfg/quickfix/CheckCfgQuickfixes.java +++ b/com.avaloq.tools.ddk.checkcfg.core/src/com/avaloq/tools/ddk/checkcfg/quickfix/CheckCfgQuickfixes.java @@ -33,8 +33,25 @@ private CheckCfgQuickfixes() { /** * Removes duplicate language configurations from the {@link CheckConfiguration} containing the given validator, by - * merging the contents (catalog and parameter configurations) of every later occurrence of the same language into the - * first occurrence and deleting the duplicates. + * appending the catalog and parameter configurations of every later occurrence of the same language onto the first + * occurrence in their original order, then deleting the now-redundant later blocks. + *

+ * The merge is a deliberate order-preserving concatenation: it relocates entries but never deduplicates, + * reorders, or otherwise rewrites them. This keeps it behaviour-preserving with respect to generation: the properties + * generator applies configurations sequentially with later definitions overriding earlier ones, so moving all entries + * into a single block in their original order yields exactly the same generated output as the original duplicate + * blocks did. Deduplicating or recursively merging the moved entries was tried and deliberately reverted, because any + * scheme that removes the resulting language-scoped duplicates also drops catalog- and check-level parameters that the + * generator would otherwise have emitted, i.e. it trades a cosmetic duplicate for real data loss. + *

+ *

+ * As a consequence the result may still contain entries that are duplicate at language scope (for example the same + * catalog or parameter configured under two of the merged blocks). Language-scoped catalog and parameter uniqueness is + * not validated anywhere today; that is a pre-existing gap, independent of and out of scope for this fix, which + * implements only what the originating issue asks for (duplicate-language detection plus a merge quick-fix). + * Such residual duplicates are benign at generation time, since the generator resolves them with the same last-wins + * semantics, and are left for a separate change that adds the missing language-scoped validation. + *

* * @param element * a configured language validator flagged as a duplicate; the merge target is derived from its containing From 7133f27308142bfd78652cd63d293f872b0edd25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Dinis=20Ferreira?= Date: Sun, 14 Jun 2026 12:01:07 +0200 Subject: [PATCH 4/4] fix: drop the merge quick-fix, keep only duplicate-language validation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To the reviewer: Your fourth-round finding stands and I have verified it directly: the generator collects inherited parameters by walking parent ConfigurableSections and taking the first occurrence of each name per section (first-wins). So collapsing two duplicate language blocks into one — which any merge must do — moves both blocks' language-level parameters into a single section, and the inner catalogs silently inherit the first value instead of their original per-block value. That is a real generated-output change, and no consolidating merge can avoid it without materializing the generator's full inheritance resolution down into every catalog/check scope, which is disproportionate. This is not specific to my implementation: the rescoping (and the unvalidated language-scoped duplicates from the earlier rounds) is intrinsic to the "merge duplicate language blocks" idea as originally conceived in #104 back in 2018; only the dedup data-loss from fdcf35066 was mine, and that was reverted. The merge quick-fix was never safely automatable. So this commit removes the quick-fix entirely and keeps the part that is correct and valuable: the error-severity validation that flags duplicate language configurations (checkConfiguredLanguageUnique, IssueCodes. DUPLICATE_LANGUAGE_CONFIGURATION, and its test). The correct resolution of a duplicate depends on which inherited value the author intended per block — intent a quick-fix cannot infer and would silently guess wrong — so manual resolution guided by the error is the right behaviour. Removed: the @Fix removeDuplicateLanguageConfiguration in CheckCfgQuickfixProvider, the UI-independent CheckCfgQuickfixes core class and its tests, the checkcfg.quickfix package export, the suite entry, and the now-unused UI message keys. Issue #103 asked for "validation and a quick-fix"; we deliver the validation now, and the merge quick-fix is dropped as unsound. Adding language-scoped catalog/parameter uniqueness validation (a pre-existing gap) is left as a separate follow-up. Co-Authored-By: Claude Fable 5 --- .../quickfix/CheckCfgQuickfixesTest.java | 135 ------------------ .../ddk/checkcfg/test/CheckCfgTestSuite.java | 2 - .../META-INF/MANIFEST.MF | 1 - .../checkcfg/quickfix/CheckCfgQuickfixes.java | 84 ----------- .../ui/quickfix/CheckCfgQuickfixProvider.java | 20 --- .../ddk/checkcfg/ui/quickfix/Messages.java | 2 - .../checkcfg/ui/quickfix/messages.properties | 2 - 7 files changed, 246 deletions(-) delete mode 100644 com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/quickfix/CheckCfgQuickfixesTest.java delete mode 100644 com.avaloq.tools.ddk.checkcfg.core/src/com/avaloq/tools/ddk/checkcfg/quickfix/CheckCfgQuickfixes.java diff --git a/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/quickfix/CheckCfgQuickfixesTest.java b/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/quickfix/CheckCfgQuickfixesTest.java deleted file mode 100644 index 64edb9e63..000000000 --- a/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/quickfix/CheckCfgQuickfixesTest.java +++ /dev/null @@ -1,135 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ -package com.avaloq.tools.ddk.checkcfg.quickfix; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import java.util.List; - -import org.eclipse.xtext.testing.InjectWith; -import org.eclipse.xtext.testing.extensions.InjectionExtension; -import org.eclipse.xtext.testing.util.ParseHelper; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import com.avaloq.tools.ddk.checkcfg.CheckCfgUiInjectorProvider; -import com.avaloq.tools.ddk.checkcfg.checkcfg.CheckConfiguration; -import com.avaloq.tools.ddk.checkcfg.checkcfg.ConfiguredLanguageValidator; -import com.google.inject.Inject; - -/** - * Tests the UI-independent model transformation behind the duplicate-language-configuration quickfix - * ({@link CheckCfgQuickfixes#mergeDuplicateLanguageConfigurations(ConfiguredLanguageValidator)}). - */ -@InjectWith(CheckCfgUiInjectorProvider.class) -@ExtendWith(InjectionExtension.class) -@SuppressWarnings("nls") -public class CheckCfgQuickfixesTest { - - private static final String LANG = "com.avaloq.tools.ddk.^check.TestLanguage"; - private static final String OTHER_LANG = "org.example.OtherLanguage"; - private static final String DUPLICATES_REMOVED = "duplicates removed"; - - @Inject - private ParseHelper parser; - - /** Builds a {@code for { }} validator block. */ - private static String forBlock(final String language, final String body) { - return " for " + language + " {\n " + body + "\n }\n"; - } - - /** Assembles the given validator blocks into a single check configuration source. */ - private static String source(final String... blocks) { - return "check configuration Test\n" + String.join("", blocks); - } - - private static List validators(final CheckConfiguration model) { - return model.getLanguageValidatorConfigurations(); - } - - /** Two occurrences of the same language, each with a distinct catalog, merge into one validator holding both catalogs. */ - @Test - public void testMergesDistinctCatalogs() throws Exception { - final CheckConfiguration model = parser.parse(source( - forBlock(LANG, "catalog a.CatA { default CheckA }"), - forBlock(LANG, "catalog b.CatB { default CheckB }"))); - CheckCfgQuickfixes.mergeDuplicateLanguageConfigurations(validators(model).get(0)); - assertEquals(1, validators(model).size(), DUPLICATES_REMOVED); - assertEquals(2, validators(model).get(0).getCatalogConfigurations().size(), "both catalogs merged into the survivor"); - } - - /** The inherited parameter-configuration list is merged too, not just catalogs. */ - @Test - public void testMergesParameterConfigurations() throws Exception { - final CheckConfiguration model = parser.parse(source( - forBlock(LANG, "integrationRelevant = true"), - forBlock(LANG, "nameOverrides = #['x']"))); - CheckCfgQuickfixes.mergeDuplicateLanguageConfigurations(validators(model).get(0)); - assertEquals(1, validators(model).size(), DUPLICATES_REMOVED); - assertEquals(2, validators(model).get(0).getParameterConfigurations().size(), "both parameters merged into the survivor"); - } - - /** More than two occurrences all collapse into the first. */ - @Test - public void testMergesThreeOccurrences() throws Exception { - final CheckConfiguration model = parser.parse(source( - forBlock(LANG, "catalog a.CatA { default CheckA }"), - forBlock(LANG, "catalog b.CatB { default CheckB }"), - forBlock(LANG, "catalog c.CatC { default CheckC }"))); - CheckCfgQuickfixes.mergeDuplicateLanguageConfigurations(validators(model).get(0)); - assertEquals(1, validators(model).size(), "all duplicates removed"); - assertEquals(3, validators(model).get(0).getCatalogConfigurations().size(), "all three catalogs merged into the survivor"); - } - - /** Only the targeted language is merged; an unrelated duplicated language is left untouched. */ - @Test - public void testOnlyTargetLanguageMerged() throws Exception { - final CheckConfiguration model = parser.parse(source( - forBlock(LANG, "catalog a.CatA { default CheckA }"), - forBlock(OTHER_LANG, "catalog b.CatB { default CheckB }"), - forBlock(LANG, "catalog c.CatC { default CheckC }"), - forBlock(OTHER_LANG, "catalog d.CatD { default CheckD }"))); - CheckCfgQuickfixes.mergeDuplicateLanguageConfigurations(validators(model).get(0)); - assertEquals(3, validators(model).size(), "only the two occurrences of the target language collapse to one"); - assertEquals(2, validators(model).stream().filter(v -> OTHER_LANG.equals(v.getLanguage())).count(), "the other duplicated language is untouched"); - } - - /** The result is independent of which duplicate occurrence the fix is invoked from. */ - @Test - public void testIndependentOfInvokedOccurrence() throws Exception { - final CheckConfiguration fromFirst = parser.parse(source( - forBlock(LANG, "catalog a.CatA { default CheckA }"), - forBlock(LANG, "catalog b.CatB { default CheckB }"))); - CheckCfgQuickfixes.mergeDuplicateLanguageConfigurations(validators(fromFirst).get(0)); - - final CheckConfiguration fromLast = parser.parse(source( - forBlock(LANG, "catalog a.CatA { default CheckA }"), - forBlock(LANG, "catalog b.CatB { default CheckB }"))); - CheckCfgQuickfixes.mergeDuplicateLanguageConfigurations(validators(fromLast).get(1)); - - assertEquals(1, validators(fromFirst).size(), "invoking from the first occurrence merges"); - assertEquals(1, validators(fromLast).size(), "invoking from the last occurrence merges identically"); - assertEquals(validators(fromFirst).get(0).getCatalogConfigurations().size(), - validators(fromLast).get(0).getCatalogConfigurations().size(), "same merged content either way"); - } - - /** An empty duplicate is simply removed; no crash, survivor unchanged. */ - @Test - public void testEmptyDuplicateRemoved() throws Exception { - final CheckConfiguration model = parser.parse(source( - forBlock(LANG, "catalog a.CatA { default CheckA }"), - forBlock(LANG, ""))); - CheckCfgQuickfixes.mergeDuplicateLanguageConfigurations(validators(model).get(0)); - assertEquals(1, validators(model).size(), "empty duplicate removed"); - assertEquals(1, validators(model).get(0).getCatalogConfigurations().size(), "survivor's catalog preserved"); - } - -} diff --git a/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/test/CheckCfgTestSuite.java b/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/test/CheckCfgTestSuite.java index c4360b03c..155b8855e 100644 --- a/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/test/CheckCfgTestSuite.java +++ b/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/test/CheckCfgTestSuite.java @@ -14,7 +14,6 @@ import org.junit.platform.suite.api.Suite; import com.avaloq.tools.ddk.checkcfg.contentassist.CheckCfgContentAssistTest; -import com.avaloq.tools.ddk.checkcfg.quickfix.CheckCfgQuickfixesTest; import com.avaloq.tools.ddk.checkcfg.scoping.CheckCfgScopeProviderTest; import com.avaloq.tools.ddk.checkcfg.syntax.CheckCfgSyntaxTest; import com.avaloq.tools.ddk.checkcfg.validation.CheckCfgConfiguredParameterValidationsTest; @@ -30,7 +29,6 @@ // @Format-Off CheckCfgConfiguredParameterValidationsTest.class, CheckCfgContentAssistTest.class, - CheckCfgQuickfixesTest.class, CheckCfgScopeProviderTest.class, CheckCfgSyntaxTest.class, CheckCfgTest.class, diff --git a/com.avaloq.tools.ddk.checkcfg.core/META-INF/MANIFEST.MF b/com.avaloq.tools.ddk.checkcfg.core/META-INF/MANIFEST.MF index 982fd8ecd..484177c1b 100644 --- a/com.avaloq.tools.ddk.checkcfg.core/META-INF/MANIFEST.MF +++ b/com.avaloq.tools.ddk.checkcfg.core/META-INF/MANIFEST.MF @@ -32,7 +32,6 @@ Export-Package: com.avaloq.tools.ddk.checkcfg, com.avaloq.tools.ddk.checkcfg.jvmmodel, com.avaloq.tools.ddk.checkcfg.parser.antlr, com.avaloq.tools.ddk.checkcfg.parser.antlr.internal, - com.avaloq.tools.ddk.checkcfg.quickfix, com.avaloq.tools.ddk.checkcfg.services, com.avaloq.tools.ddk.checkcfg.validation, com.avaloq.tools.ddk.checkcfg.serializer diff --git a/com.avaloq.tools.ddk.checkcfg.core/src/com/avaloq/tools/ddk/checkcfg/quickfix/CheckCfgQuickfixes.java b/com.avaloq.tools.ddk.checkcfg.core/src/com/avaloq/tools/ddk/checkcfg/quickfix/CheckCfgQuickfixes.java deleted file mode 100644 index 186f4d446..000000000 --- a/com.avaloq.tools.ddk.checkcfg.core/src/com/avaloq/tools/ddk/checkcfg/quickfix/CheckCfgQuickfixes.java +++ /dev/null @@ -1,84 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ -package com.avaloq.tools.ddk.checkcfg.quickfix; - -import java.util.ArrayList; -import java.util.List; - -import org.eclipse.xtext.EcoreUtil2; - -import com.avaloq.tools.ddk.checkcfg.checkcfg.CheckConfiguration; -import com.avaloq.tools.ddk.checkcfg.checkcfg.ConfiguredLanguageValidator; - -/** - * Model transformations backing the Check Configuration quickfixes. - *

- * These operate purely on the EMF model and carry no UI dependency, so they can be unit-tested without an editor or - * workbench. The UI quickfix provider is a thin wrapper that invokes them from an {@code ISemanticModification}. - *

- */ -public final class CheckCfgQuickfixes { - - private CheckCfgQuickfixes() { - // utility class - } - - /** - * Removes duplicate language configurations from the {@link CheckConfiguration} containing the given validator, by - * appending the catalog and parameter configurations of every later occurrence of the same language onto the first - * occurrence in their original order, then deleting the now-redundant later blocks. - *

- * The merge is a deliberate order-preserving concatenation: it relocates entries but never deduplicates, - * reorders, or otherwise rewrites them. This keeps it behaviour-preserving with respect to generation: the properties - * generator applies configurations sequentially with later definitions overriding earlier ones, so moving all entries - * into a single block in their original order yields exactly the same generated output as the original duplicate - * blocks did. Deduplicating or recursively merging the moved entries was tried and deliberately reverted, because any - * scheme that removes the resulting language-scoped duplicates also drops catalog- and check-level parameters that the - * generator would otherwise have emitted, i.e. it trades a cosmetic duplicate for real data loss. - *

- *

- * As a consequence the result may still contain entries that are duplicate at language scope (for example the same - * catalog or parameter configured under two of the merged blocks). Language-scoped catalog and parameter uniqueness is - * not validated anywhere today; that is a pre-existing gap, independent of and out of scope for this fix, which - * implements only what the originating issue asks for (duplicate-language detection plus a merge quick-fix). - * Such residual duplicates are benign at generation time, since the generator resolves them with the same last-wins - * semantics, and are left for a separate change that adds the missing language-scoped validation. - *

- * - * @param element - * a configured language validator flagged as a duplicate; the merge target is derived from its containing - * configuration and its language name, so the result is independent of which duplicate occurrence is passed - */ - public static void mergeDuplicateLanguageConfigurations(final ConfiguredLanguageValidator element) { - final CheckConfiguration configuration = EcoreUtil2.getContainerOfType(element, CheckConfiguration.class); - final String languageName = element.getLanguage(); - if (configuration == null || languageName == null) { - return; - } - ConfiguredLanguageValidator first = null; - final List duplicates = new ArrayList(); - for (final ConfiguredLanguageValidator validator : configuration.getLanguageValidatorConfigurations()) { - if (languageName.equals(validator.getLanguage())) { - if (first == null) { - first = validator; - } else { - duplicates.add(validator); - } - } - } - for (final ConfiguredLanguageValidator duplicate : duplicates) { - first.getParameterConfigurations().addAll(duplicate.getParameterConfigurations()); - first.getCatalogConfigurations().addAll(duplicate.getCatalogConfigurations()); - configuration.getLanguageValidatorConfigurations().remove(duplicate); - } - } - -} diff --git a/com.avaloq.tools.ddk.checkcfg.ui/src/com/avaloq/tools/ddk/checkcfg/ui/quickfix/CheckCfgQuickfixProvider.java b/com.avaloq.tools.ddk.checkcfg.ui/src/com/avaloq/tools/ddk/checkcfg/ui/quickfix/CheckCfgQuickfixProvider.java index 31b824800..387fdc43d 100644 --- a/com.avaloq.tools.ddk.checkcfg.ui/src/com/avaloq/tools/ddk/checkcfg/ui/quickfix/CheckCfgQuickfixProvider.java +++ b/com.avaloq.tools.ddk.checkcfg.ui/src/com/avaloq/tools/ddk/checkcfg/ui/quickfix/CheckCfgQuickfixProvider.java @@ -26,9 +26,7 @@ import com.avaloq.tools.ddk.checkcfg.checkcfg.CheckConfiguration; import com.avaloq.tools.ddk.checkcfg.checkcfg.ConfiguredCatalog; import com.avaloq.tools.ddk.checkcfg.checkcfg.ConfiguredCheck; -import com.avaloq.tools.ddk.checkcfg.checkcfg.ConfiguredLanguageValidator; import com.avaloq.tools.ddk.checkcfg.checkcfg.SeverityKind; -import com.avaloq.tools.ddk.checkcfg.quickfix.CheckCfgQuickfixes; import com.avaloq.tools.ddk.checkcfg.validation.IssueCodes; @@ -161,22 +159,4 @@ public void apply(final EObject element, final IModificationContext context) { }); } - /** - * Removes duplicate language configurations by merging the contents of every later occurrence into the first one - * and deleting the duplicates. - * - * @param issue - * the issue - * @param acceptor - * the acceptor - */ - @Fix(IssueCodes.DUPLICATE_LANGUAGE_CONFIGURATION) - public void removeDuplicateLanguageConfiguration(final Issue issue, final IssueResolutionAcceptor acceptor) { - acceptor.accept(issue, Messages.CheckCfgQuickfixProvider_REMOVE_DUPLICATE_LANG_LABEL, Messages.CheckCfgQuickfixProvider_REMOVE_DUPLICATE_LANG_DESCN, null, new ISemanticModification() { - @Override - public void apply(final EObject element, final IModificationContext context) { - CheckCfgQuickfixes.mergeDuplicateLanguageConfigurations((ConfiguredLanguageValidator) element); - } - }); - } } diff --git a/com.avaloq.tools.ddk.checkcfg.ui/src/com/avaloq/tools/ddk/checkcfg/ui/quickfix/Messages.java b/com.avaloq.tools.ddk.checkcfg.ui/src/com/avaloq/tools/ddk/checkcfg/ui/quickfix/Messages.java index 3cb496a88..22dfc2036 100644 --- a/com.avaloq.tools.ddk.checkcfg.ui/src/com/avaloq/tools/ddk/checkcfg/ui/quickfix/Messages.java +++ b/com.avaloq.tools.ddk.checkcfg.ui/src/com/avaloq/tools/ddk/checkcfg/ui/quickfix/Messages.java @@ -28,8 +28,6 @@ public class Messages extends NLS { public static String CheckCfgQuickfixProvider_REMOVE_DUPLICATE_CATALOG_LABEL; public static String CheckCfgQuickfixProvider_REMOVE_DUPLICATE_CHECK_DESCN; public static String CheckCfgQuickfixProvider_REMOVE_DUPLICATE_CHECK_LABEL; - public static String CheckCfgQuickfixProvider_REMOVE_DUPLICATE_LANG_DESCN; - public static String CheckCfgQuickfixProvider_REMOVE_DUPLICATE_LANG_LABEL; public static String CheckCfgQuickfixProvider_REMOVE_DUPLICATE_PARAM_DESCN; public static String CheckCfgQuickfixProvider_REMOVE_DUPLICATE_PARAM_LABEL; diff --git a/com.avaloq.tools.ddk.checkcfg.ui/src/com/avaloq/tools/ddk/checkcfg/ui/quickfix/messages.properties b/com.avaloq.tools.ddk.checkcfg.ui/src/com/avaloq/tools/ddk/checkcfg/ui/quickfix/messages.properties index 33b296a44..539316d64 100644 --- a/com.avaloq.tools.ddk.checkcfg.ui/src/com/avaloq/tools/ddk/checkcfg/ui/quickfix/messages.properties +++ b/com.avaloq.tools.ddk.checkcfg.ui/src/com/avaloq/tools/ddk/checkcfg/ui/quickfix/messages.properties @@ -8,7 +8,5 @@ CheckCfgQuickfixProvider_REMOVE_DUPLICATE_CATALOG_DESCN=Remove the duplicate cat CheckCfgQuickfixProvider_REMOVE_DUPLICATE_CATALOG_LABEL=Remove duplicate catalog CheckCfgQuickfixProvider_REMOVE_DUPLICATE_CHECK_DESCN=Remove the duplicate check configuration. CheckCfgQuickfixProvider_REMOVE_DUPLICATE_CHECK_LABEL=Remove duplicate check -CheckCfgQuickfixProvider_REMOVE_DUPLICATE_LANG_DESCN=Merge the duplicate language configurations into the first occurrence and remove the duplicates. -CheckCfgQuickfixProvider_REMOVE_DUPLICATE_LANG_LABEL=Merge duplicate language configurations CheckCfgQuickfixProvider_REMOVE_DUPLICATE_PARAM_DESCN=Remove the duplicate parameter configuration. CheckCfgQuickfixProvider_REMOVE_DUPLICATE_PARAM_LABEL=Remove duplicate parameter \ No newline at end of file