361 lines
11 KiB
Go
361 lines
11 KiB
Go
package sourcemap
|
|
|
|
import (
|
|
"errors"
|
|
"slices"
|
|
"strings"
|
|
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath"
|
|
"github.com/go-json-experiment/json"
|
|
)
|
|
|
|
type (
|
|
SourceIndex int
|
|
NameIndex int
|
|
)
|
|
|
|
const (
|
|
sourceIndexNotSet SourceIndex = -1
|
|
nameIndexNotSet NameIndex = -1
|
|
notSet int = -1
|
|
)
|
|
|
|
type Generator struct {
|
|
pathOptions tspath.ComparePathsOptions
|
|
file string
|
|
sourceRoot string
|
|
sourcesDirectoryPath string
|
|
rawSources []string
|
|
sources []string
|
|
sourceToSourceIndexMap map[string]SourceIndex
|
|
sourcesContent []*string
|
|
names []string
|
|
nameToNameIndexMap map[string]NameIndex
|
|
mappings strings.Builder
|
|
lastGeneratedLine int
|
|
lastGeneratedCharacter int
|
|
lastSourceIndex SourceIndex
|
|
lastSourceLine int
|
|
lastSourceCharacter int
|
|
lastNameIndex NameIndex
|
|
hasLast bool
|
|
pendingGeneratedLine int
|
|
pendingGeneratedCharacter int
|
|
pendingSourceIndex SourceIndex
|
|
pendingSourceLine int
|
|
pendingSourceCharacter int
|
|
pendingNameIndex NameIndex
|
|
hasPending bool
|
|
hasPendingSource bool
|
|
hasPendingName bool
|
|
}
|
|
|
|
type RawSourceMap struct {
|
|
Version int `json:"version"`
|
|
File string `json:"file"`
|
|
SourceRoot string `json:"sourceRoot"`
|
|
Sources []string `json:"sources"`
|
|
Names []string `json:"names"`
|
|
Mappings string `json:"mappings"`
|
|
SourcesContent []*string `json:"sourcesContent,omitzero"`
|
|
}
|
|
|
|
func NewGenerator(file string, sourceRoot string, sourcesDirectoryPath string, options tspath.ComparePathsOptions) *Generator {
|
|
return &Generator{
|
|
file: file,
|
|
sourceRoot: sourceRoot,
|
|
sourcesDirectoryPath: sourcesDirectoryPath,
|
|
pathOptions: options,
|
|
}
|
|
}
|
|
|
|
func (gen *Generator) Sources() []string { return gen.rawSources }
|
|
|
|
// Adds a source to the source map
|
|
func (gen *Generator) AddSource(fileName string) SourceIndex {
|
|
source := tspath.GetRelativePathToDirectoryOrUrl(
|
|
gen.sourcesDirectoryPath,
|
|
fileName,
|
|
true, /*isAbsolutePathAnUrl*/
|
|
gen.pathOptions,
|
|
)
|
|
|
|
sourceIndex, found := gen.sourceToSourceIndexMap[source]
|
|
if !found {
|
|
sourceIndex = SourceIndex(len(gen.sources))
|
|
gen.sources = append(gen.sources, source)
|
|
gen.rawSources = append(gen.rawSources, fileName)
|
|
if gen.sourceToSourceIndexMap == nil {
|
|
gen.sourceToSourceIndexMap = make(map[string]SourceIndex)
|
|
}
|
|
gen.sourceToSourceIndexMap[source] = sourceIndex
|
|
}
|
|
|
|
return sourceIndex
|
|
}
|
|
|
|
// Sets the content for a source
|
|
func (gen *Generator) SetSourceContent(sourceIndex SourceIndex, content string) error {
|
|
if sourceIndex < 0 || int(sourceIndex) >= len(gen.sources) {
|
|
return errors.New("sourceIndex is out of range")
|
|
}
|
|
for len(gen.sourcesContent) <= int(sourceIndex) {
|
|
gen.sourcesContent = append(gen.sourcesContent, nil)
|
|
}
|
|
gen.sourcesContent[sourceIndex] = &content
|
|
return nil
|
|
}
|
|
|
|
// Declares a name in the source map, returning the index of the name
|
|
func (gen *Generator) AddName(name string) NameIndex {
|
|
nameIndex, found := gen.nameToNameIndexMap[name]
|
|
if !found {
|
|
nameIndex = NameIndex(len(gen.names))
|
|
gen.names = append(gen.names, name)
|
|
if gen.nameToNameIndexMap == nil {
|
|
gen.nameToNameIndexMap = make(map[string]NameIndex)
|
|
}
|
|
gen.nameToNameIndexMap[name] = nameIndex
|
|
}
|
|
return nameIndex
|
|
}
|
|
|
|
func (gen *Generator) isNewGeneratedPosition(generatedLine int, generatedCharacter int) bool {
|
|
return !gen.hasPending ||
|
|
gen.pendingGeneratedLine != generatedLine ||
|
|
gen.pendingGeneratedCharacter != generatedCharacter
|
|
}
|
|
|
|
func (gen *Generator) isBacktrackingSourcePosition(sourceIndex SourceIndex, sourceLine int, sourceCharacter int) bool {
|
|
return sourceIndex != sourceIndexNotSet &&
|
|
sourceLine != notSet &&
|
|
sourceCharacter != notSet &&
|
|
gen.pendingSourceIndex == sourceIndex &&
|
|
(gen.pendingSourceLine > sourceLine ||
|
|
gen.pendingSourceLine == sourceLine && gen.pendingSourceCharacter > sourceCharacter)
|
|
}
|
|
|
|
func (gen *Generator) shouldCommitMapping() bool {
|
|
return gen.hasPending && (!gen.hasLast ||
|
|
gen.lastGeneratedLine != gen.pendingGeneratedLine ||
|
|
gen.lastGeneratedCharacter != gen.pendingGeneratedCharacter ||
|
|
gen.lastSourceIndex != gen.pendingSourceIndex ||
|
|
gen.lastSourceLine != gen.pendingSourceLine ||
|
|
gen.lastSourceCharacter != gen.pendingSourceCharacter ||
|
|
gen.lastNameIndex != gen.pendingNameIndex)
|
|
}
|
|
|
|
func (gen *Generator) appendMappingCharCode(charCode rune) {
|
|
gen.mappings.WriteRune(charCode)
|
|
}
|
|
|
|
func (gen *Generator) appendBase64VLQ(inValue int) {
|
|
// Add a new least significant bit that has the sign of the value.
|
|
// if negative number the least significant bit that gets added to the number has value 1
|
|
// else least significant bit value that gets added is 0
|
|
// eg. -1 changes to binary : 01 [1] => 3
|
|
// +1 changes to binary : 01 [0] => 2
|
|
if inValue < 0 {
|
|
inValue = ((-inValue) << 1) + 1
|
|
} else {
|
|
inValue = inValue << 1
|
|
}
|
|
|
|
// Encode 5 bits at a time starting from least significant bits
|
|
for {
|
|
currentDigit := inValue & 31 // 11111
|
|
inValue = inValue >> 5
|
|
if inValue > 0 {
|
|
// There are still more digits to decode, set the msb (6th bit)
|
|
currentDigit = currentDigit | 32
|
|
}
|
|
gen.appendMappingCharCode(base64FormatEncode(currentDigit))
|
|
if inValue <= 0 {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
func (gen *Generator) commitPendingMapping() {
|
|
if !gen.shouldCommitMapping() {
|
|
return
|
|
}
|
|
|
|
// Line/Comma delimiters
|
|
if gen.lastGeneratedLine < gen.pendingGeneratedLine {
|
|
// Emit line delimiters
|
|
for {
|
|
gen.appendMappingCharCode(';')
|
|
gen.lastGeneratedLine++
|
|
if gen.lastGeneratedLine >= gen.pendingGeneratedLine {
|
|
break
|
|
}
|
|
}
|
|
// Only need to set this once
|
|
gen.lastGeneratedCharacter = 0
|
|
} else {
|
|
if gen.lastGeneratedLine != gen.pendingGeneratedLine {
|
|
// panic rather than error as an invariant has been violated
|
|
panic("generatedLine cannot backtrack")
|
|
}
|
|
// Emit comma to separate the entry
|
|
if gen.hasLast {
|
|
gen.appendMappingCharCode(',')
|
|
}
|
|
}
|
|
|
|
// 1. Relative generated character
|
|
gen.appendBase64VLQ(gen.pendingGeneratedCharacter - gen.lastGeneratedCharacter)
|
|
gen.lastGeneratedCharacter = gen.pendingGeneratedCharacter
|
|
|
|
if gen.hasPendingSource {
|
|
// 2. Relative sourceIndex
|
|
gen.appendBase64VLQ(int(gen.pendingSourceIndex - gen.lastSourceIndex))
|
|
gen.lastSourceIndex = gen.pendingSourceIndex
|
|
|
|
// 3. Relative source line
|
|
gen.appendBase64VLQ(gen.pendingSourceLine - gen.lastSourceLine)
|
|
gen.lastSourceLine = gen.pendingSourceLine
|
|
|
|
// 4. Relative source character
|
|
gen.appendBase64VLQ(gen.pendingSourceCharacter - gen.lastSourceCharacter)
|
|
gen.lastSourceCharacter = gen.pendingSourceCharacter
|
|
|
|
if gen.hasPendingName {
|
|
// 5. Relative nameIndex
|
|
gen.appendBase64VLQ(int(gen.pendingNameIndex - gen.lastNameIndex))
|
|
gen.lastNameIndex = gen.pendingNameIndex
|
|
}
|
|
}
|
|
|
|
gen.hasLast = true
|
|
}
|
|
|
|
func (gen *Generator) addMapping(generatedLine int, generatedCharacter int, sourceIndex SourceIndex, sourceLine int, sourceCharacter int, nameIndex NameIndex) {
|
|
if gen.isNewGeneratedPosition(generatedLine, generatedCharacter) ||
|
|
gen.isBacktrackingSourcePosition(sourceIndex, sourceLine, sourceCharacter) {
|
|
gen.commitPendingMapping()
|
|
gen.pendingGeneratedLine = generatedLine
|
|
gen.pendingGeneratedCharacter = generatedCharacter
|
|
gen.hasPendingSource = false
|
|
gen.hasPendingName = false
|
|
gen.hasPending = true
|
|
}
|
|
|
|
if sourceIndex != sourceIndexNotSet && sourceLine != notSet && sourceCharacter != notSet {
|
|
gen.pendingSourceIndex = sourceIndex
|
|
gen.pendingSourceLine = sourceLine
|
|
gen.pendingSourceCharacter = sourceCharacter
|
|
gen.hasPendingSource = true
|
|
if nameIndex != nameIndexNotSet {
|
|
gen.pendingNameIndex = nameIndex
|
|
gen.hasPendingName = true
|
|
}
|
|
}
|
|
}
|
|
|
|
// Adds a mapping without source information
|
|
func (gen *Generator) AddGeneratedMapping(generatedLine int, generatedCharacter int) error {
|
|
if generatedLine < gen.pendingGeneratedLine {
|
|
return errors.New("generatedLine cannot backtrack")
|
|
}
|
|
if generatedCharacter < 0 {
|
|
return errors.New("generatedCharacter cannot be negative")
|
|
}
|
|
gen.addMapping(generatedLine, generatedCharacter, sourceIndexNotSet, notSet /*sourceLine*/, notSet /*sourceCharacter*/, nameIndexNotSet)
|
|
return nil
|
|
}
|
|
|
|
// Adds a mapping with source information
|
|
func (gen *Generator) AddSourceMapping(generatedLine int, generatedCharacter int, sourceIndex SourceIndex, sourceLine int, sourceCharacter int) error {
|
|
if generatedLine < gen.pendingGeneratedLine {
|
|
return errors.New("generatedLine cannot backtrack")
|
|
}
|
|
if generatedCharacter < 0 {
|
|
return errors.New("generatedCharacter cannot be negative")
|
|
}
|
|
if sourceIndex < 0 || int(sourceIndex) >= len(gen.sources) {
|
|
return errors.New("sourceIndex is out of range")
|
|
}
|
|
if sourceLine < 0 {
|
|
return errors.New("sourceLine cannot be negative")
|
|
}
|
|
if sourceCharacter < 0 {
|
|
return errors.New("sourceCharacter cannot be negative")
|
|
}
|
|
gen.addMapping(generatedLine, generatedCharacter, sourceIndex, sourceLine, sourceCharacter, nameIndexNotSet)
|
|
return nil
|
|
}
|
|
|
|
// Adds a mapping with source and name information
|
|
func (gen *Generator) AddNamedSourceMapping(generatedLine int, generatedCharacter int, sourceIndex SourceIndex, sourceLine int, sourceCharacter int, nameIndex NameIndex) error {
|
|
if generatedLine < gen.pendingGeneratedLine {
|
|
return errors.New("generatedLine cannot backtrack")
|
|
}
|
|
if generatedCharacter < 0 {
|
|
return errors.New("generatedCharacter cannot be negative")
|
|
}
|
|
if sourceIndex < 0 || int(sourceIndex) >= len(gen.sources) {
|
|
return errors.New("sourceIndex is out of range")
|
|
}
|
|
if sourceLine < 0 {
|
|
return errors.New("sourceLine cannot be negative")
|
|
}
|
|
if sourceCharacter < 0 {
|
|
return errors.New("sourceCharacter cannot be negative")
|
|
}
|
|
if nameIndex < 0 || int(nameIndex) >= len(gen.names) {
|
|
return errors.New("nameIndex is out of range")
|
|
}
|
|
gen.addMapping(generatedLine, generatedCharacter, sourceIndex, sourceLine, sourceCharacter, nameIndex)
|
|
return nil
|
|
}
|
|
|
|
// Gets the source map as a `RawSourceMap` object
|
|
func (gen *Generator) RawSourceMap() *RawSourceMap {
|
|
gen.commitPendingMapping()
|
|
sources := slices.Clone(gen.sources)
|
|
if sources == nil {
|
|
sources = []string{}
|
|
}
|
|
names := slices.Clone(gen.names)
|
|
if names == nil {
|
|
names = []string{}
|
|
}
|
|
return &RawSourceMap{
|
|
Version: 3,
|
|
File: gen.file,
|
|
SourceRoot: gen.sourceRoot,
|
|
Sources: sources,
|
|
Names: names,
|
|
Mappings: gen.mappings.String(),
|
|
SourcesContent: slices.Clone(gen.sourcesContent),
|
|
}
|
|
}
|
|
|
|
// Gets the string representation of the source map
|
|
func (gen *Generator) String() string {
|
|
buf, err := json.Marshal(gen.RawSourceMap())
|
|
if err != nil {
|
|
panic(err.Error())
|
|
}
|
|
return string(buf)
|
|
}
|
|
|
|
func base64FormatEncode(value int) rune {
|
|
switch {
|
|
case value >= 0 && value < 26:
|
|
return 'A' + rune(value)
|
|
case value >= 26 && value < 52:
|
|
return 'a' + rune(value) - 26
|
|
case value >= 52 && value < 62:
|
|
return '0' + rune(value) - 52
|
|
case value == 62:
|
|
return '+'
|
|
case value == 63:
|
|
return '/'
|
|
default:
|
|
panic("not a base64 value")
|
|
}
|
|
}
|