Skip to content

Commit c190c93

Browse files
committed
Add docs and tidy
1 parent 2e3ea87 commit c190c93

39 files changed

+28006
-62
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@
44
/*.xcodeproj
55
xcuserdata/
66
default.profraw
7+
/build

.jazzy.yaml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
author: John Fairhurst
2+
author_url: http://github.com/johnfairh
3+
copyright: Distributed under the MIT license. Maintained by [John Fairhurst](mailto:[email protected]).
4+
readme: README.md
5+
products:
6+
- docs
7+
- docset
8+
code_host: github
9+
code_host_url: https://github.com/johnfairh/SourceMapper
10+
code_host_file_url: https://github.com/johnfairh/SourceMapper/blob/main
11+
clean: true
12+
sdk: macosx
13+
theme: fw2020
14+
deployment_url: https://johnfairh.github.io/SourceMapper/
15+
build_tool: spm
16+
modules: SourceMapper
17+
module_version: v1.0.0

README.md

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,64 @@
1-
wip
1+
<!--
2+
SourceMapper
3+
README.md
4+
Distributed under the MIT license, see LICENSE.
5+
-->
6+
7+
![Platforms](https://img.shields.io/badge/platform-macOS%20%7C%20linux-lightgrey.svg)
8+
[![codecov](https://codecov.io/gh/johnfairh/SourceMapper/branch/main/graph/badge.svg?token=0NAP6IA9EB)](https://codecov.io/gh/johnfairh/SourceMapper)
9+
![Tests](https://github.com/johnfairh/SourceMapper/workflows/Tests/badge.svg)
10+
11+
# SourceMapper
12+
13+
Simple Swift implementation of the
14+
[SourceMap](https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k)
15+
specification: create, load, query, modify, and save source maps.
16+
17+
## Examples
18+
19+
```swift
20+
let map = try SourceMap(data: try Data(contentsOf: mapURL))
21+
let segment = map.map(line: 12, column: 0)
22+
```
23+
24+
```swift
25+
let map = SourceMap()
26+
map.sources = [.remote("a.scss")]
27+
map.sourceRoot = "./../src/"
28+
map.setSegments(...)
29+
let mapData = try map.encode()
30+
```
31+
32+
## Documentation
33+
34+
* [API documentation](https://johnfairh.github.io/SourceMapper/)
35+
* [Dash docset](https://johnfairh.github.io/SourceMapper/docsets/SourceMapper.tgz)
36+
37+
No support for:
38+
* Extension fields
39+
* Index map format
40+
41+
## Requirements
42+
43+
* Swift 5.3
44+
* macOS 10.10 (tested on macOS 11.0 IA64)
45+
* Linux (tested on Ubuntu 18.04.5)
46+
47+
## Installation
48+
49+
Only with Swift Package Manager, via Xcode or directly:
50+
51+
Package dependency:
52+
```swift
53+
.package(name: "SourceMapper",
54+
url: "https://github.com/johnfairh/SourceMapper.git",
55+
from: "1.0.0")
56+
```
57+
58+
## Contributions
59+
60+
Welcome: open an issue / [email protected] / @johnfairh
61+
62+
## License
63+
64+
Distributed under the MIT license.

Sources/SourceMapper/Errors.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public enum SourceMapError: Error, CustomStringConvertible, Equatable {
1414
/// Source map decoding failed because the `sources` and `sourcesContent` fields have different cardinalities.
1515
case inconsistentSources(sourcesCount: Int, sourcesContentCount: Int)
1616

17-
/// Source map decoding failed because of a bad character in the `mappings` field.
17+
/// Source map decoding failed because of an invalid character in the `mappings` field.
1818
case invalidBase64Character(Character)
1919

2020
/// Source map decoding failed because a VLQ sequence does not terminate properly.
@@ -29,7 +29,7 @@ public enum SourceMapError: Error, CustomStringConvertible, Equatable {
2929
/// Source map encoding failed because a name index is out of range.
3030
case invalidName(Int, count: Int)
3131

32-
/// A short human-readable description of the error
32+
/// A short human-readable description of the error.
3333
public var description: String {
3434
switch self {
3535
case .invalidFormat(let format):

Sources/SourceMapper/JSON.swift

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ extension SourceMap {
2323
/// - parameter data: The source map JSON.
2424
/// - parameter checkMappings: Whether to validate the mappings part of the source map. By default
2525
/// this is `false` meaning that the mappings are only validated if `getSegments()` is called later on.
26-
/// Mappings validation is somewhat costly in time and memory and is not be necessary for all uses.
27-
/// - throws: If the JSON is bad, the version is bad, or if mandatory fields are missing.
28-
/// The mappings aren't decoded until accessed.
26+
/// Mappings validation is somewhat costly in time and memory and is not necessary for all uses.
27+
/// - throws: If the JSON is bad, the version is bad, or if mandatory fields are missing. Some error if
28+
/// `checkMappings` is set and the mappings are invalid.
2929
public convenience init(data: Data, checkMappings: Bool = false) throws {
3030
let decoded = try JSONDecoder().decode(SerializedSourceMap.self, from: data)
3131
if decoded.version != SourceMap.VERSION {
@@ -47,10 +47,7 @@ extension SourceMap {
4747
}
4848

4949
self.sources = zip(decoded.sources, contents).map {
50-
if let sourceContent = $1 {
51-
return .inline(url: $0, content: sourceContent)
52-
}
53-
return .remote(url: $0)
50+
Source(url: $0, content: $1)
5451
}
5552

5653
self.names = decoded.names
@@ -73,11 +70,10 @@ extension SourceMap {
7370

7471
/// Validate any customizations and encode the source map as JSON
7572
///
76-
/// - parameter continueOnError: Set the error handling policy. If `false` then
77-
/// any inconsistencies in the mapping data cause an error to be thrown, otherwise they
78-
/// are passed through to the JSON format.
73+
/// - parameter continueOnError: If `false` then any inconsistencies in the mappings
74+
/// cause an error to be thrown, otherwise they are passed through to the JSON format.
7975
///
80-
/// The default is `true` which is probably right when working with existing sourcemaps,
76+
/// The default is `true` which is probably right when working with existing source maps,
8177
/// but if you're creating from scratch it may be more useful to set `false` to catch bugs
8278
/// in your generation code.
8379
///
@@ -108,7 +104,7 @@ extension SourceMap {
108104

109105
/// Validate any customizations and encode the source map as JSON in a string
110106
///
111-
/// See `encode(contineOnError:)`.
107+
/// See `encode(continueOnError:)`.
112108
public func encodeString(continueOnError: Bool = true) throws -> String {
113109
String(data: try encode(continueOnError: continueOnError), encoding: .utf8)!
114110
}

Sources/SourceMapper/Mappings.swift

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ extension SourceMap {
1313
///
1414
/// Decodes the mappings if necessary.
1515
/// - throws: If the mappings are undecodable in some way indicating a corrupt source map.
16-
/// No error is thrown for invalid indicies - an `invalidSegment` is substituted and the offence
17-
/// reported in `invalidSegmentReports`.
16+
/// Invalid indices do not cause errors.
1817
public func getSegments() throws -> [[Segment]] {
1918
if let segments = segments {
2019
return segments
@@ -71,13 +70,13 @@ extension SourceMap {
7170

7271
/// Map a location in the generated code to its source.
7372
///
74-
/// All `name` indicies are guaranteed valid at time of call, any out of range are replaced with
73+
/// All `name` indices are guaranteed valid at time of call: any out of range are replaced with
7574
/// `nil` before being returned. All `source` indices are either valid at time of call or as
7675
/// requested via the `invalidSourcePos` parameter.
7776
///
7877
/// - parameter line: 0-based index of the line in the generated code file.
7978
/// - parameter column: 0-based index of the column in `rowIndex`.
80-
/// - parameter invalidSourcePos: value to substitute for any decoded `SourcePos`
79+
/// - parameter invalidSourcePos: Value to substitute for any decoded `SourcePos`
8180
/// that is invalid, ie. refers to a `source` that is out of range. Default `nil`.
8281
/// - throws: If the mappings can't be decoded. See `getSegments()`.
8382
/// - returns: The mapping segment, or `nil` if there is no mapping for the row.

Sources/SourceMapper/SourceMap.swift

Lines changed: 34 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,17 @@
77
//
88
import Foundation
99

10-
/// Don't support extension fields ("x_|whatever|"): nobody seems to use them, even the one in the spec
11-
/// seems unknown today, and it immensely complicates the JSON layer.
10+
/// A source map describing how each segment of a generated file corresponds to some original source file.
1211
///
13-
/// 1 - open a sourcemap, mess around with it a bit, rewrite it,
14-
/// 2 - join multiple sourcemaps together
15-
/// 3 - open a sourcemap and use it, doing location queries including source file locations
16-
/// 4 - create a new sourcemap from scratch and write it to a file.
12+
/// The main use cases imagined are:
13+
/// 1. Read a source map `init(data:checkMappings:)` and query it `map(...)`.
14+
/// 2. Read a source map, make minor modifications, write it back `encode(...)`.
15+
/// 3. Create a new source map `init(version:)`, fill in fields, and write it.
1716
///
17+
/// There are two representations of the actual mappings. The `mappings` property holds
18+
/// the compacted mapping string that looks like `AAAA;EACA`. These can be decoded into
19+
/// arrays of `Segment`s. These arrays can be very large and time-consuming to create, and
20+
/// so they are usually generated on-demand via `getSegments()`.
1821
public final class SourceMap {
1922
/// Create an empty source map.
2023
public init(version: Int = SourceMap.VERSION) {
@@ -37,31 +40,25 @@ public final class SourceMap {
3740
/// The name of the generated code file with which the source map is associated.
3841
public var file: String?
3942

40-
/// Value to prepend to each `sources` url before attempting their resolution.
43+
/// Value to prepend to each `sources` URL before attempting their resolution.
4144
public var sourceRoot: String?
4245

4346
/// The location and content of an original source referred to from the source map.
4447
///
4548
/// Use `getSourceURL(...)`to interpret source URLs incorporating `sourceRoot`.
46-
public enum Source {
47-
case remote(url: String)
48-
case inline(url: String, content: String)
49-
49+
public struct Source {
5050
/// The URL recorded in the source map for this source.
51-
/// - see: `SourceMap.getSourceURL(...)`.
52-
public var url: String {
53-
switch self {
54-
case .remote(let url),
55-
.inline(let url, _): return url
56-
}
57-
}
51+
///
52+
/// See: `SourceMap.getSourceURL(...)`.
53+
public let url: String
5854

5955
/// The content, if any, recorded in the source map for this source.
60-
public var content: String? {
61-
switch self {
62-
case .remote: return nil
63-
case .inline(_, let content): return content
64-
}
56+
public let content: String?
57+
58+
/// Initialize a new Source.
59+
public init(url: String, content: String? = nil) {
60+
self.url = url
61+
self.content = content
6562
}
6663
}
6764

@@ -76,12 +73,12 @@ public final class SourceMap {
7673

7774
/// Get the URL of a source, incorporating the `sourceRoot` if set.
7875
///
79-
/// - parameter sourceIndex: The index into `sources` to look up.
76+
/// - parameter source: The index into `sources` to look up.
8077
/// - parameter sourceMapURL: The absolute URL of this source map -- relative source URLs
8178
/// are interpreted relative to this base.
82-
public func getSourceURL(sourceIndex: Int, sourceMapURL: URL) -> URL {
83-
precondition(sourceIndex < sources.count)
84-
let sourceURLString = (sourceRoot ?? "") + sources[sourceIndex].url
79+
public func getSourceURL(source: Int, sourceMapURL: URL) -> URL {
80+
precondition(source < sources.count)
81+
let sourceURLString = (sourceRoot ?? "") + sources[source].url
8582
if let sourceURL = URL(string: sourceURLString),
8683
sourceURL.scheme != nil {
8784
return sourceURL
@@ -104,7 +101,7 @@ public final class SourceMap {
104101
public let source: Int32
105102
/// 0-based line index into the original source file.
106103
public let line: Int32
107-
/// 0-based column index into line `lineIndex`.
104+
/// 0-based column index into line `line`.
108105
public let column: Int32
109106
/// 0-based index into `SourceMap.names` of any name associated with
110107
/// the mapping segment, or `nil` if there is none.
@@ -136,7 +133,7 @@ public final class SourceMap {
136133
/// 0-based column in the generated code that ends the segment, or `nil`
137134
/// indicating 'until either the next segment or the end of the line'.
138135
///
139-
/// This field is optional and advisory - it's not stored in the sourcemap itself, rather
136+
/// This field is optional and advisory - it's not stored in the source map itself, rather
140137
/// calculated (guessed) from the next `firstColumn` value. Its value is not
141138
/// used in comparisons between two `Segment`s.
142139
public internal(set) var lastColumn: Int32?
@@ -170,20 +167,23 @@ public final class SourceMap {
170167
sourcePos: sourcePos)
171168
}
172169

173-
/// Compare two segments. The `lastColumn` value is not included.
170+
/// Compare two segments. The `lastColumn` value is not included. :nodoc:
174171
public static func == (lhs: Segment, rhs: Segment) -> Bool {
175172
lhs.firstColumn == rhs.firstColumn &&
176173
lhs.sourcePos == rhs.sourcePos
177174
}
178175

179-
/// Hash the segment.
176+
/// Hash the segment. :nodoc:
180177
public func hash(into hasher: inout Hasher) {
181178
hasher.combine(firstColumn)
182179
hasher.combine(sourcePos)
183180
}
184181
}
185182

186-
/// The mappings in their compacted format
183+
/// The mappings in their compacted format.
184+
///
185+
/// If you use `setSegments(...)` to change the segments then this field is not updated to match
186+
/// until the next call to `encode(...)` so be careful reading it during this window.
187187
public internal(set) var mappings: String
188188

189189
/// Track consistency between `mappings` and `_mappingSegments`.
@@ -215,7 +215,7 @@ extension SourceMap.Segment: CustomStringConvertible {
215215
extension SourceMap {
216216
/// A formatted multi-line string describing the mapping segments.
217217
///
218-
/// - throws: something if the mapping segments can't be decoded from the source.
218+
/// - throws: If the mapping segments can't be decoded.
219219
public func getSegmentsDescription() throws -> String {
220220
var line = 0
221221
var lines: [String] = []
@@ -239,6 +239,7 @@ extension SourceMap {
239239
}
240240

241241
extension SourceMap: CustomStringConvertible {
242+
/// A short description of the source map.
242243
public var description: String {
243244
var str = "SourceMap(v=\(version)"
244245
if let file = file {

Tests/SourceMapperTests/TestBasics.swift

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ class TestBasics: XCTestCase {
6161

6262
map.file = "myfile.css"
6363
map.sourceRoot = "../dist"
64-
map.sources = [.remote(url: "a.scss")]
64+
map.sources = [.init(url: "a.scss")]
6565
map.names = ["fred", "barney"]
6666
XCTAssertEqual(#"SourceMap(v=3 file="myfile.css" sourceRoot="../dist" #sources=1 #names=2 mappings="???")"#, map.description)
6767

@@ -74,19 +74,19 @@ class TestBasics: XCTestCase {
7474

7575
func testSourceURL() throws {
7676
let map = SourceMap()
77-
map.sources = [.remote(url: "http://host/path/a.scss"),
78-
.remote(url: "../dist/b.scss"),
79-
.remote(url: "c.scss")]
77+
map.sources = [.init(url: "http://host/path/a.scss"),
78+
.init(url: "../dist/b.scss"),
79+
.init(url: "c.scss")]
8080
let mapURL = URL(fileURLWithPath: "/web/main.map")
8181

82-
let source1 = map.getSourceURL(sourceIndex: 0, sourceMapURL: mapURL)
82+
let source1 = map.getSourceURL(source: 0, sourceMapURL: mapURL)
8383
XCTAssertEqual("http://host/path/a.scss", source1.absoluteString)
8484

85-
let source2 = map.getSourceURL(sourceIndex: 1, sourceMapURL: mapURL)
85+
let source2 = map.getSourceURL(source: 1, sourceMapURL: mapURL)
8686
XCTAssertEqual("file:///dist/b.scss", source2.absoluteString)
8787

8888
map.sourceRoot = "./../dist/"
89-
let source3 = map.getSourceURL(sourceIndex: 2, sourceMapURL: mapURL)
89+
let source3 = map.getSourceURL(source: 2, sourceMapURL: mapURL)
9090
XCTAssertEqual("file:///dist/c.scss", source3.absoluteString)
9191
}
9292
}

Tests/SourceMapperTests/TestMappings.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ class TestMappings: XCTestCase {
3030
/// Basic stepping through mappings coder, +ve, -ve, multi-byte, non-zero offsets.
3131
func testNormalMappingScenarios() throws {
3232
let map = SourceMap()
33-
map.sources = [.remote(url: "source1.css"), .remote(url: "source2.css")]
33+
map.sources = [.init(url: "source1.css"), .init(url: "source2.css")]
3434
map.names = ["Name1", "Name2"]
3535
let line1: [SourceMap.Segment] = [
3636
.init(firstColumn: 0),
@@ -48,7 +48,7 @@ class TestMappings: XCTestCase {
4848
/// Encode failures
4949
func testEncodeFailures() throws {
5050
let map = SourceMap()
51-
map.sources = [.remote(url: "source1.css")]
51+
map.sources = [.init(url: "source1.css")]
5252

5353
map.setSegments([[.init(columns: 0..<8, sourcePos: .some(.init(source: 1, line: 0, column: 0)))]])
5454
XCTAssertSourceMapError(.invalidSource(1, count: 1)) {
@@ -64,7 +64,7 @@ class TestMappings: XCTestCase {
6464
/// Map failures
6565
func testMapFailures() throws {
6666
let map = SourceMap()
67-
map.sources = [.remote(url: "source1.css")]
67+
map.sources = [.init(url: "source1.css")]
6868
map.names = ["name1"]
6969

7070
map.setSegments([

0 commit comments

Comments
 (0)