Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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<CheckConfiguration> parser;

/** Builds a {@code for <language> { <body> }} 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<ConfiguredLanguageValidator> 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");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -29,6 +30,7 @@
// @Format-Off
CheckCfgConfiguredParameterValidationsTest.class,
CheckCfgContentAssistTest.class,
CheckCfgQuickfixesTest.class,
CheckCfgScopeProviderTest.class,
CheckCfgSyntaxTest.class,
CheckCfgTest.class,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

}
1 change: 1 addition & 0 deletions com.avaloq.tools.ddk.checkcfg.core/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
* <p>
* 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}.
* </p>
*/
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<ConfiguredLanguageValidator> duplicates = new ArrayList<ConfiguredLanguageValidator>();
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);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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<ConfiguredLanguageValidator> predicate = new Predicate<ConfiguredLanguageValidator>() {
@Override
public boolean apply(final ConfiguredLanguageValidator validator) {
return validator.getLanguage() != null;
}
};
Function<ConfiguredLanguageValidator, String> function = new Function<ConfiguredLanguageValidator, String>() {
@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.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@
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;


Expand Down Expand Up @@ -158,4 +160,23 @@ 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);
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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