Skip to content

Conversation

takikawa
Copy link
Collaborator

@takikawa takikawa commented Apr 9, 2025

This PR is an initial attempt at specifying how we can use Decoded Source Map Records to do position lookups:

  • Adds a step to sort the mappings before they are added into the record, which helps simplify the spec algorithms
    • This allows some refactoring for index map handling
  • Adds algorithms for looking up original positions

Specifying something like this helps proposals like the range mapping proposal that affect or change how mappings should be interpreted.

@takikawa takikawa changed the title Add algorithms for retrieving original & generated positions Spec: add algorithms for retrieving original & generated positions Apr 9, 2025
@takikawa
Copy link
Collaborator Author

I have some changes to this that I'll push after #194 is merged based on the recent TG4 discussion (adding a LUB variant of lookup for example).

@takikawa takikawa force-pushed the mapping-operations branch from ba24fd2 to 976b509 Compare July 16, 2025 00:03
@takikawa
Copy link
Collaborator Author

I finally made another revision to this, with the PR rebased on the latest spec and changing how the operations work a bit.

Instead of providing multiple versions of GetOriginalPosition, the current PR just defines a single version that returns an exact match or the greatest lower bound. I believe the three major browser devtools all provide this, and while some provide additional versions of the operation, the spec doesn't preclude that either.

For GetGeneratedPositions there is less agreement between implementations. So this PR defines a version GetGeneratedPositionsForOriginalLine which returns all the reverse mappings that match the given original source + line. Some implementations do match on the column in some way too, but you can filter the output of this operation to get that. The idea is that this is a baseline that can be used to derive the behaviors in all the implementations.


For additional context, I summarized the browser devtools behavior for these two operations:

Getting the original position:

  • Chrome: findEntry: takes line, column, and optional inlineFrameIndex arguments. Finds equal or greatest lower bound item.
  • Firefox: originalPositionFor: takes line, column, and bias arguments. Finds equal or lub/glb based on the bias arguments.
    • jridgewell/trace-mapping also implements the same API
    • Both lub/glb are actually used, since for example you'd want lub search if you are searching at a fixed column=0 on multiple lines.
  • WebKit: findOriginalPosition: takes line, column arguments. Returns equal or glb.

Getting generated positions:

  • Chrome: findReverseEntries: takes source, line, column. Finds equal or glb reverse mapping and then returns a list of mappings with that original position.
  • Firefox: allGeneratedPositionsFor: takes source, line, column. Finds either all exact matches (if column passed) or all mappings on source+line if column is null.
  • WebKit: doesn't implement an equivalent, but there is a single-mapping version. It returns the first column position on the matching line.

@takikawa
Copy link
Collaborator Author

There's a rendered preview of the PR here.

spec.emu Outdated
1. If the result of performing ComparePositions(_mapping_.[[GeneratedPosition]], _generatedPosition_) is ~greater~, then
1. Return _closest_.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's some disagreement between impls here. When there are multiple mappings with the same generated position, this spec will return the last one.

  • Chrome agrees, because it's upperBound uses >= and moves rightward
  • Safari agrees for the same reason
  • Mozilla agrees, but uses Rust's binary_search_by which states it could be any result an is subject to change
    • It also always returns the last match, regardless of bias.
  • trace-mapping will return the first match when using glb, and the last match when using lup.
    • Mozilla's old JS impl would return the any match, and I wanted something deterministic regardless of adding unrelated mappings elsewhere. 😬
Suggested change
1. If the result of performing ComparePositions(_mapping_.[[GeneratedPosition]], _generatedPosition_) is ~greater~, then
1. Return _closest_.
1. If the result of performing ComparePositions(_mapping_.[[GeneratedPosition]], _generatedPosition_) is not ~lesser~, then
1. Return _closest_.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that's a good point about multiple matching mappings; I hadn't reached a conclusion on how to handle it. I wonder if the spec should just allow an arbitrary matching mapping to be returned when there are multiple.

BTW, for your suggested change, wouldn't that fail to return a matching with an equal generated position if there is one? (_closest_ would need to be updated first?).

Introduces a new section with algorithms for using Decoded Source Map Records
in order to perform lookups. The section is intended to have multiple
operations in the future but only has one for now.

Additionally, this introduces an explicit sort of the mappings
when they are put in a decoded source map record.
@takikawa takikawa changed the title Spec: add algorithms for retrieving original & generated positions Spec: add algorithm for retrieving origina position Sep 1, 2025
@takikawa takikawa marked this pull request as ready for review September 1, 2025 23:23
@takikawa
Copy link
Collaborator Author

takikawa commented Sep 1, 2025

I updated this PR to just add the original position operation, since that's a more obvious starting point and lets other PRs build on it.

One thing that's unresolved is what to do when there are duplicate mappings at the same location. We could change the algorithm to return an arbitrary mapping when picking the closest element:

Current (pick last closest):

        1. Else,
          1. Set _closest_ to _mapping_.

Arbitrary:

        1. Else,
          1. If the result of performing ComparePositions(_mapping_.[[GeneratedPosition]], _closest_) is ~equal~, then
            1. Set _closest_ to an implementation-defined choice of _closest_ or _mapping_.
          1. Else, set _closest_ to _mapping_.

Any opinion on this? (or other options)

@takikawa takikawa changed the title Spec: add algorithm for retrieving origina position Spec: add algorithm for retrieving original position Sep 1, 2025
This makes more sense given the name of the function
GetOriginalPosition (
_sourceMapRecord_: a Decoded Source Map Record,
_generatedPosition_: a Position Record,
): an Original Position Record or *null*
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also changed this to return the original position record instead of the entry. Given the name of the operation it probably makes more sense to return the position record, and that also makes a bit more sense for the range mappings proposal spec text (since you just return a modified position, rather than making up a new mapping with a modified position).

This should also be ok for PR #219 which only uses the original position part.

@@ -683,6 +683,7 @@
1. Let _sources_ be DecodeSourceMapSources(_baseURL_, _sourceRootField_, _sourcesField_, _sourcesContentField_, _ignoreListField_).
1. Let _namesField_ be GetOptionalListOfStrings(_json_, *"names"*).
1. Let _mappings_ be DecodeMappings(_mappingsField_, _namesField_, _sources_).
1. [declared="a,b"] Sort _mappings_ in ascending order, with a Decoded Mapping Record _a_ being less than a Decoded Mapping Record _b_ if ComparePositions(_a_.[[GeneratedPosition]], _b_.[[GeneratedPosition]]) is ~lesser~.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why move this?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previously the sort was only done below in the index map algorithm to check validity. It's moved here so that decoded mappings are guaranteed to be sorted, because otherwise the search algorithm for looking up positions is slower.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants