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

Range Mappings: Allow multi-line ranges #169

Merged
merged 3 commits into from
Mar 6, 2025
Merged
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
33 changes: 18 additions & 15 deletions proposals/range-mappings.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ Tobias Koppers
## Motiviation

Currently mappings map locations in the source code to other locations in the source code.
This works well when trying to access a location that is defined in the SourceMap, but one looses percision when accessing locations that are not directly defined in the SourceMap.
This works well when trying to access a location that is defined in the SourceMap, but one loses precision when accessing locations that are not directly defined in the SourceMap.
In these cases tools usually fallback to next lower column that is actually mapped in the SourceMap.
So we are either loosing information or we need many mappings in the SourceMap to cover all possible locations.
So we are either losing information or we need many mappings in the SourceMap to cover all possible locations.

These information problem is especially problematic when applying a SourceMap to another SourceMap.
Here we can only use locations that are specified in both SourceMaps. We have to be lucky that locations match up.
Expand All @@ -26,7 +26,7 @@ Here we can only use locations that are specified in both SourceMaps. We have to

As an example let's look at a build process when a TypeScript file is converted to JavaScript first and that is minified afterwards.

The TypeScript to JavaScript transformation is mostly keeping code identical, but removing type annotations.
A simplistic TypeScript to JavaScript transformation such as SWC's [`strip_types`](https://play.swc.rs/?version=1.10.7&code=H4sIAAAAAAAAA0WMQQqDMBBF93OKv6wgPYBpu5HewAvEQTA0Tcpkggvx7k2E4OrD%2B4%2FH3qaE0epjemEn4Jdn7xjb6tJnkTQg5O%2B8iLkutc4PmAwVxDEklcwa5cYxB21%2B37TurAJagvdWxROnba6r6gXXqfSgg0hXiRveIqXemT8eTB9GqwAAAA%3D%3D&config=H4sIAAAAAAAAA1VPOw7DIAzdOQXy3KFi6NA79BCIOhERAYQdqSjK3QsJpM1mv4%2Ff8yqkhIkMPOVaxrJEnQjTuReEsmf9KQhwjkgm2chw6yxTpQbtCHdoOxhgnUbk6kJSd6WaA1wIhN3RsNl6O%2BT%2FTBPmmJDoKqxS7UeH10TRUmEO72Un2y%2B179HgAT9RDzsPg6VXd3JaUGxfBMLf3xcBAAA%3D&strip-types=) keeps the runtime code identical, whilst removing type annotations.
Theoretically only a few SourceMap mappings are needs, as most code stays identical.

Minifying is a bigger transformation of the code, which one it's own would result in a lot of SourceMap mappings to be generated.
Expand All @@ -40,7 +40,7 @@ The TypeScript SourceMap would behave identical to a SourceMap mapping every sin
## Proposal

Add a boolean flag for each mapping to convert it into a "range mapping".
For a range mapping, tools should assume that every char that follows the mapping (until the next mapping), is mapped to the specified original location plus the offset in the generated code.
For a range mapping, tools should assume that every char (including newlines) that follows the mapping (until the next mapping), is mapped to the specified original location plus the offset in the generated code.
This means all chars in the generated code that is covered by the range mapping, are mapped char by char to the same range in the original code.
(Usually this only makes sense when generated and original are identical for that range)

Expand All @@ -49,27 +49,30 @@ This means all chars in the generated code that is covered by the range mapping,
Generated Code:

``` js
console.log("hello world");
console.log(
"hello world");
```

Original Code:

``` js
// Copyright 2023
console.log("hello world");
// Copyright 2023
console.log(
"hello world");
```

With a normal mapping:

```
Source Map:
Generate Line 1 Column 0 -> Original Line 2 Column 2
Generate Line 2 Column 0 -> Original Line 3 Column 0
```

``` js
console.log("hello world");
^ ^ ^
| | + maps to Original Line 2 Column 2
console.log(\n"hello world");
^ ^ ^
| | + maps to Original Line 3 Column 0
| + maps to Original Line 2 Column 2
+ maps to Original Line 2 Column 2
```
Expand All @@ -82,17 +85,17 @@ Generate Line 1 Column 0 -> Original Line 2 Column 2 (range mapping)
```

``` js
console.log("hello world");
^ ^ ^
| | + maps to Original Line 2 Column 14
console.log(\n"hello world");
^ ^ ^
| | + maps to Original Line 3 Column 0
| + maps to Original Line 2 Column 10
+ maps to Original Line 2 Column 2
```

### Encoding

To avoid a breaking change to the `mappings` field, a new field named `rangeMappings` is added.
It contains encoded data per line in the generated code.
It contains encoded data per-line in the generated code.
Each line is separated by `;`.
The data contains a bit per mapping in that line.
When the bit is set, the mapping is a range mapping, otherwise it is a normal mapping.
Expand All @@ -111,5 +114,5 @@ Line 1: 0b000000 0b000000 0b000001 => the 13th mapping is a range mapping
Line 3: 0b100000 => the 6th mapping is a range mapping
```

> Note: The per line encoding is chosen to make it easier to generate SourceMap line by line.
> Note: The per-line encoding is chosen to make it easier to generate SourceMap line by line.
> It also looks similar to the `mappings` field, so should allow good compression.