From 4296349f3ebb6ab4ae4de8b6b658e89101c2a7cd Mon Sep 17 00:00:00 2001 From: Moritz Eysholdt Date: Tue, 21 Apr 2015 15:49:13 +0200 Subject: [PATCH] [465003] fixed 'ITextRegionAccess cannot handle duplicate terminal rule' https://bugs.eclipse.org/bugs/show_bug.cgi?id=465003 Signed-off-by: Moritz Eysholdt --- .../regionaccess/ITextRegionAccess.java | 6 +- .../internal/AbstractRegionAccess.java | 32 +++++++ .../internal/RegionAccessAccessTest.xtend | 88 +++++++++++++++---- 3 files changed, 109 insertions(+), 17 deletions(-) diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/ITextRegionAccess.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/ITextRegionAccess.java index 636cc2982..56bde2d60 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/ITextRegionAccess.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/ITextRegionAccess.java @@ -147,7 +147,7 @@ public interface ITextRegionAccess { * @return a text region that reaches from the beginning of its first semantic region to the end of its last * semantic region. */ - IEObjectRegion regionForEObject(EObject object); // TODO: should be semantic region? + IEObjectRegion regionForEObject(EObject object); /** * @return returns the first {@link ISemanticRegion} that represents the value of {@code owner.eGet(feature)}. May @@ -176,6 +176,10 @@ public interface ITextRegionAccess { */ ISemanticRegion regionForRuleCallTo(EObject owner, AbstractRule rule); + ISemanticRegion regionForRuleCall(EObject owner, RuleCall ruleCall); + + List regionsForRuleCalls(EObject owner, RuleCall... ruleCalls); + List regionsForAllEObjects(); /** diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/AbstractRegionAccess.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/AbstractRegionAccess.java index 8369f8944..d9bfd4d61 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/AbstractRegionAccess.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/AbstractRegionAccess.java @@ -209,6 +209,38 @@ public abstract class AbstractRegionAccess implements ITextRegionAccess { return null; } + @Override + public ISemanticRegion regionForRuleCall(EObject owner, RuleCall ruleCall) { + assertNoEObjectRuleCall(ruleCall); + AbstractEObjectRegion tokens = regionForEObject(owner); + if (tokens == null) + return null; + for (ISemanticRegion region : tokens.getSemanticLeafRegions()) + if (region.getGrammarElement() == ruleCall) + return region; + return null; + } + + @Override + public List regionsForRuleCalls(EObject owner, RuleCall... ruleCalls) { + for (int i = 0; i < ruleCalls.length; i++) + assertNoEObjectRuleCall(ruleCalls[i]); + AbstractEObjectRegion tokens = regionForEObject(owner); + if (tokens == null) + return null; + List result = Lists.newArrayList(); + for (ISemanticRegion region : tokens.getSemanticLeafRegions()) + for (int i = 0; i < ruleCalls.length; i++) + if (region.getGrammarElement() == ruleCalls[i]) + result.add(region); + return ImmutableList.copyOf(result); + } + + protected void assertNoEObjectRuleCall(RuleCall ruleCall) { + if (GrammarUtil.isEObjectRuleCall(ruleCall)) + throw new IllegalStateException("Only Enum, Datatype and Terminal Rule Calls allowed."); + } + @Override public ITextSegment regionForOffset(int offset, int length) { return new TextSegment(this, offset, length); diff --git a/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/formatting2/regionaccess/internal/RegionAccessAccessTest.xtend b/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/formatting2/regionaccess/internal/RegionAccessAccessTest.xtend index 2560a10d6..f2e381553 100644 --- a/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/formatting2/regionaccess/internal/RegionAccessAccessTest.xtend +++ b/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/formatting2/regionaccess/internal/RegionAccessAccessTest.xtend @@ -9,11 +9,15 @@ package org.eclipse.xtext.formatting2.regionaccess.internal import com.google.inject.Inject import com.google.inject.Provider +import java.util.Collection import org.eclipse.emf.ecore.EObject import org.eclipse.xtext.formatting2.regionaccess.ITextRegionAccess +import org.eclipse.xtext.formatting2.regionaccess.ITextSegment import org.eclipse.xtext.formatting2.regionaccess.TextRegionAccessBuilder +import org.eclipse.xtext.formatting2.regionaccess.internal.regionaccesstestlanguage.AssignedAction import org.eclipse.xtext.formatting2.regionaccess.internal.regionaccesstestlanguage.Mixed import org.eclipse.xtext.formatting2.regionaccess.internal.regionaccesstestlanguage.Root +import org.eclipse.xtext.formatting2.regionaccess.internal.services.RegionAccessTestLanguageGrammarAccess import org.eclipse.xtext.junit4.InjectWith import org.eclipse.xtext.junit4.XtextRunner import org.eclipse.xtext.junit4.util.ParseHelper @@ -24,7 +28,7 @@ import org.junit.runner.RunWith import static org.eclipse.xtext.formatting2.regionaccess.internal.regionaccesstestlanguage.RegionaccesstestlanguagePackage.Literals.* import static org.junit.Assert.* -import org.eclipse.xtext.formatting2.regionaccess.internal.regionaccesstestlanguage.AssignedAction +import com.google.common.collect.ImmutableList /** * @author Moritz Eysholdt - Initial contribution and API @@ -35,47 +39,92 @@ class RegionAccessAccessTest { @Inject extension ParseHelper parseHelper @Inject Provider textRegionAccessBuilder @Inject extension ValidationTestHelper validationTestHelper + @Inject extension RegionAccessTestLanguageGrammarAccess @Test def void regionForFeatureAttribute() { val mixed = '''6 (foo)'''.parseAs(Mixed) val access = mixed.toAccess val actual = access.regionForFeature(mixed, MIXED__NAME) - assertEquals("foo", actual.text) + val actuals = access.regionsForFeatures(mixed, MIXED__NAME) + assertEquals("foo", actual, actuals) } @Test def void regionForFeatureCrossReference() { val mixed = '''6 (ref foo) action (foo) end'''.parseAs(AssignedAction) val access = mixed.toAccess val actual = access.regionForFeature(mixed.child, MIXED__REF) - assertEquals("foo", actual.text) + val actuals = access.regionsForFeatures(mixed.child, MIXED__REF) + assertEquals("foo", actual, actuals) } - @Test(expected=IllegalStateException) def void regionForFeatureContainmentReference() { + def void regionForFeatureContainmentReference() { val mixed = '''6 (foo) action'''.parseAs(AssignedAction) val access = mixed.toAccess - val actual = access.regionForFeature(mixed, ASSIGNED_ACTION__CHILD) - assertEquals("foo", actual.text) + try { + access.regionForFeature(mixed, ASSIGNED_ACTION__CHILD) + fail() + } catch (IllegalStateException e) { + } + try { + access.regionsForFeatures(mixed, ASSIGNED_ACTION__CHILD) + fail() + } catch (IllegalStateException e) { + } } - @Test def void regionsForFeaturesAttribute() { + @Test def void regionForRuleCallUnassignedTerminal() { + val mixed = '''6 (unassigned foo)'''.parseAs(Mixed) + val access = mixed.toAccess + val actual = access.regionForRuleCall(mixed, mixedAccess.IDTerminalRuleCall_1_1_0) + val actuals = access.regionsForRuleCalls(mixed, mixedAccess.IDTerminalRuleCall_1_1_0) + assertEquals("foo", actual, actuals) + } + + @Test def void regionForRuleCallUnassignedDataType() { + val mixed = '''6 (unassigned datatype foo)'''.parseAs(Mixed) + val access = mixed.toAccess + val actual = access.regionForRuleCall(mixed, mixedAccess.datatypeParserRuleCall_1_1_1) + val actuals = access.regionsForRuleCalls(mixed, mixedAccess.datatypeParserRuleCall_1_1_1) + assertEquals("datatype foo", actual, actuals) + } + + @Test def void regionForRuleCallAssignedTerminal() { val mixed = '''6 (foo)'''.parseAs(Mixed) val access = mixed.toAccess - val actual = access.regionsForFeatures(mixed, MIXED__NAME) - assertEquals("foo", actual.map[text].join) + val actual = access.regionForRuleCall(mixed, mixedAccess.nameIDTerminalRuleCall_2_2_0_0) + val actuals = access.regionForRuleCall(mixed, mixedAccess.nameIDTerminalRuleCall_2_2_0_0) + assertEquals("foo", actual, actuals) } - @Test def void regionsForFeaturesCrossReference() { + @Test def void regionForRuleCallAssignedDataType() { + val mixed = '''6 (datatype foo)'''.parseAs(Mixed) + val access = mixed.toAccess + val actual = access.regionForRuleCall(mixed, mixedAccess.datatypeDatatypeParserRuleCall_2_2_2_0) + val actuals = access.regionForRuleCall(mixed, mixedAccess.datatypeDatatypeParserRuleCall_2_2_2_0) + assertEquals("datatype foo", actual, actuals) + } + + @Test def void regionForRuleCallCrossReference() { val mixed = '''6 (ref foo) action (foo) end'''.parseAs(AssignedAction) val access = mixed.toAccess - val actual = access.regionsForFeatures(mixed.child, MIXED__REF) - assertEquals("foo", actual.map[text].join) + val actual = access.regionForRuleCall(mixed.child, mixedAccess.refMixedIDTerminalRuleCall_2_2_3_1_0_1) + val actuals = access.regionsForRuleCalls(mixed.child, mixedAccess.refMixedIDTerminalRuleCall_2_2_3_1_0_1) + assertEquals("foo", actual, actuals) } - @Test(expected=IllegalStateException) def void regionsForFeaturesContainmentReference() { - val mixed = '''6 (foo) action'''.parseAs(AssignedAction) + def void regionForRuleCallEObjectParserRule() { + val mixed = '''6 (child foo)'''.parseAs(Mixed) val access = mixed.toAccess - val actual = access.regionsForFeatures(mixed, ASSIGNED_ACTION__CHILD) - assertEquals("foo", actual.map[text].join) + try { + access.regionForRuleCall(mixed, mixedAccess.eobjMixedParserRuleCall_2_2_1_1_0) + fail() + } catch (IllegalStateException e) { + } + try { + access.regionsForRuleCalls(mixed, mixedAccess.eobjMixedParserRuleCall_2_2_1_1_0) + fail() + } catch (IllegalStateException e) { + } } def private parseAs(CharSequence seq, Class cls) { @@ -87,4 +136,11 @@ class RegionAccessAccessTest { def private ITextRegionAccess toAccess(EObject obj) { return textRegionAccessBuilder.get.forNodeModel(obj.eResource as XtextResource).create } + + def private void assertEquals(String expected, ITextSegment single, Collection regions) { + assertEquals(expected, single.text) + assertEquals(1, regions.size) + assertEquals(expected, regions.head.text) + assertTrue(regions instanceof ImmutableList) + } } \ No newline at end of file