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/2] 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/2] 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); } }); }