From c89159f64d1421fe8d1839fe34b91ceb778d9d40 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] 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 | 80 +++++++++++++++++++ .../checkcfg/validation/CheckCfgTest.xtend | 63 --------------- .../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 + 9 files changed, 160 insertions(+), 63 deletions(-) create mode 100644 com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/validation/CheckCfgTest.java delete mode 100644 com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/validation/CheckCfgTest.xtend 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 new file mode 100644 index 000000000..e5f6d06d0 --- /dev/null +++ b/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/validation/CheckCfgTest.java @@ -0,0 +1,80 @@ +/******************************************************************************* + * 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.validation; + +import org.eclipse.xtext.testing.InjectWith; +import org.eclipse.xtext.testing.extensions.InjectionExtension; +import org.eclipse.xtext.testing.util.ParseHelper; +import org.eclipse.xtext.testing.validation.ValidationTestHelper; +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.CheckcfgPackage; +import com.google.inject.Inject; + + +@InjectWith(CheckCfgUiInjectorProvider.class) +@ExtendWith(InjectionExtension.class) +@SuppressWarnings("nls") +public class CheckCfgTest { + + @Inject + private ValidationTestHelper helper; + + @Inject + private ParseHelper parser; + + @Test + public void testValidLanguageOk() throws Exception { + final CheckConfiguration model = parser.parse(""" + check configuration Test + + for com.avaloq.tools.ddk.^check.TestLanguage { + + } + + """); + helper.assertNoIssues(model); + } + + @Test + public void testUnknownLanguageNotOk() throws Exception { + final CheckConfiguration model = parser.parse(""" + check configuration Test + + for com.avaloq.tools.ddk.^check.Unknown { + + } + + """); + 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.test/src/com/avaloq/tools/ddk/checkcfg/validation/CheckCfgTest.xtend b/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/validation/CheckCfgTest.xtend deleted file mode 100644 index db200957f..000000000 --- a/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/validation/CheckCfgTest.xtend +++ /dev/null @@ -1,63 +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.validation - -import com.avaloq.tools.ddk.checkcfg.checkcfg.CheckConfiguration -import com.avaloq.tools.ddk.checkcfg.checkcfg.CheckcfgPackage -import com.google.inject.Inject -import org.eclipse.xtext.testing.util.ParseHelper -import org.eclipse.xtext.testing.validation.ValidationTestHelper -import org.eclipse.xtext.testing.InjectWith -import com.avaloq.tools.ddk.checkcfg.CheckCfgUiInjectorProvider -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.^extension.ExtendWith -import org.eclipse.xtext.testing.extensions.InjectionExtension - -@InjectWith(typeof(CheckCfgUiInjectorProvider)) -@ExtendWith(InjectionExtension) -class CheckCfgTest { - - - @Inject - ValidationTestHelper helper; - - @Inject - ParseHelper parser; - - @Test - def testValidLanguageOk() { - - val model = parser.parse(''' - check configuration Test - - for com.avaloq.tools.ddk.^check.TestLanguage { - - } - - '''); - helper.assertNoIssues(model); - } - - @Test - def testUnknownLanguageNotOk() { - - val model = parser.parse(''' - check configuration Test - - for com.avaloq.tools.ddk.^check.Unknown { - - } - - '''); - helper.assertError(model, CheckcfgPackage.Literals::CONFIGURED_LANGUAGE_VALIDATOR, IssueCodes.UNKNOWN_LANGUAGE); - } - -} 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