Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Outdentation breaks trace links #128

Open
kolovos opened this issue Oct 17, 2024 · 1 comment
Open

Outdentation breaks trace links #128

kolovos opened this issue Oct 17, 2024 · 1 comment
Labels
bug Something isn't working
Milestone

Comments

@kolovos
Copy link
Contributor

kolovos commented Oct 17, 2024

EGL's recently-introduced outdentation feature, breaks traceability links. For example, if the template from the EGX playground example is modified as follows (outdentation added in line 6)

[*Generate a <h1> with the name of the person*]
<h1>[%=p.name%]'s Tasks</h1> 
[*Generate a table for the person's tasks*]
<table>
[*For every task*]
[%for (t in p.getTasks()){-%]
[*Generate a row with the title of the task*]
    <tr>
        <td>[%=t.title%]</td>
    </tr>
[%}%]
</table>

[%
// Returns the tasks of a person
operation Person getTasks() {
    return Task.all.select(
        t|t.effort.exists(e|e.person = self));
}
%]

the reported trace links for task titles are broken (see below)

image

This is because outdentation is implemented partly using a post-transformation formatter, and formatters are expected to deal with updating traceability themselves (which the outdentation formatter doesn't).

Given that most formatters only add/remove whitespace, we could introduce an abstract e.g. TraceabilityPreservingFormatter class that updates trace links given only the original and the formatted text and make EGL's OutdentationFormatter, as well as other formatters, extend it. To deal with cases where formatters actually do more than adding/removing whitespace, TraceabilityPreservingFormatter could actually check that the original/formatted text only differ in whitespace and fail or report a warning otherwise.

@kolovos kolovos added this to the 2.6.0 milestone Oct 17, 2024
@kolovos kolovos added the bug Something isn't working label Oct 17, 2024
@kolovos
Copy link
Contributor Author

kolovos commented Nov 20, 2024

Below is some code we could use to map offsets of formatted code back to the original code assuming the formatter has only added/removed whitespace (which is the case for the outdentation formatter)

package org.eclipse.epsilon.egl.formatter;

import java.util.ArrayList;
import java.util.List;

public class PositionMapper {

    public static List<Integer> mapOffsets(String original, String formatted) {
        
        // Helper variables to track the current index in both strings
        int originalIndex = 0, formattedIndex = 0;

        // Create an array to store the mapped offsets of each character in the original string
        int[] mappedOffsets = new int[original.length()];
        
        // Process both strings simultaneously
        while (originalIndex < original.length() && formattedIndex < formatted.length()) {
        	
            char originalChar = original.charAt(originalIndex);
            char formattedChar = formatted.charAt(formattedIndex);

            // If characters match, map the current formatted index to the original index
            if (formattedIndex < formatted.length() && originalChar == formattedChar) {
                mappedOffsets[originalIndex] = formattedIndex;
                originalIndex++;
                formattedIndex++;
            } else if (Character.isWhitespace(originalChar)) {
                // Handle cases where the characters differ by continuing through the original string
            	mappedOffsets[originalIndex] = -1;
            	originalIndex++;
            } else if (Character.isWhitespace(formattedChar)) {
            	formattedIndex++;
            }
        }

        // If there are remaining characters in the original string that weren't mapped yet
        while (originalIndex < original.length()) {
            mappedOffsets[originalIndex] = formattedIndex;  // Remaining original chars map to end of formatted
            originalIndex++;
        }
        
        ArrayList<Integer> mappedOffsetsList = new ArrayList<Integer>();
        for (int mappedOffset : mappedOffsets) mappedOffsetsList.add(mappedOffset);
        return mappedOffsetsList;
    }

    public static void main(String[] args) {
        // Example usage
        String original = "Hello\t\n\nWorld!";
        String formatted = "Hello\n\t\t World!";
        List<Integer> originalOffsets = new ArrayList<>();
        originalOffsets.add(0);  // H
        originalOffsets.add(6);  // W

        List<Integer> formattedOffsets = mapOffsets(original, formatted);

        int originalOffset = 0;
        for (Integer mappedOffset : formattedOffsets) {
        	char mappedChar = mappedOffset >= 0 ? formatted.charAt(mappedOffset) : ' ';
            System.out.println(originalOffset + "->" + mappedOffset + " / " + original.charAt(originalOffset) + "->" + mappedChar);
            originalOffset++;
        }
    }
}

@agarciadom agarciadom modified the milestones: 2.6.0, 2.7.0 Jan 3, 2025
@agarciadom agarciadom modified the milestones: 2.7.0, 2.8.0 Feb 17, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants