vendor tsgo
This commit is contained in:
1234
tools/tsgo/internal/testutil/harnessutil/harnessutil.go
Normal file
1234
tools/tsgo/internal/testutil/harnessutil/harnessutil.go
Normal file
File diff suppressed because it is too large
Load Diff
45
tools/tsgo/internal/testutil/harnessutil/recorderfs.go
Normal file
45
tools/tsgo/internal/testutil/harnessutil/recorderfs.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package harnessutil
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"sync"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/vfs"
|
||||
)
|
||||
|
||||
type OutputRecorderFS struct {
|
||||
vfs.FS
|
||||
outputsMut sync.Mutex
|
||||
outputsMap map[string]int
|
||||
outputs []*TestFile
|
||||
}
|
||||
|
||||
func NewOutputRecorderFS(fs vfs.FS) vfs.FS {
|
||||
return &OutputRecorderFS{FS: fs}
|
||||
}
|
||||
|
||||
func (fs *OutputRecorderFS) WriteFile(path string, data string) error {
|
||||
if err := fs.FS.WriteFile(path, data); err != nil {
|
||||
return err
|
||||
}
|
||||
path = fs.Realpath(path)
|
||||
fs.outputsMut.Lock()
|
||||
defer fs.outputsMut.Unlock()
|
||||
if index, ok := fs.outputsMap[path]; ok {
|
||||
fs.outputs[index] = &TestFile{UnitName: path, Content: data}
|
||||
} else {
|
||||
index := len(fs.outputs)
|
||||
if fs.outputsMap == nil {
|
||||
fs.outputsMap = make(map[string]int)
|
||||
}
|
||||
fs.outputsMap[path] = index
|
||||
fs.outputs = append(fs.outputs, &TestFile{UnitName: path, Content: data})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fs *OutputRecorderFS) Outputs() []*TestFile {
|
||||
fs.outputsMut.Lock()
|
||||
defer fs.outputsMut.Unlock()
|
||||
return slices.Clone(fs.outputs)
|
||||
}
|
||||
364
tools/tsgo/internal/testutil/harnessutil/sourcemap_recorder.go
Normal file
364
tools/tsgo/internal/testutil/harnessutil/sourcemap_recorder.go
Normal file
@@ -0,0 +1,364 @@
|
||||
package harnessutil
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/core"
|
||||
"github.com/microsoft/typescript-go/internal/json"
|
||||
"github.com/microsoft/typescript-go/internal/scanner"
|
||||
"github.com/microsoft/typescript-go/internal/sourcemap"
|
||||
"github.com/microsoft/typescript-go/internal/stringutil"
|
||||
)
|
||||
|
||||
type writerAggregator struct {
|
||||
strings.Builder
|
||||
}
|
||||
|
||||
func (w *writerAggregator) WriteStringf(format string, args ...any) {
|
||||
w.WriteString(fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func (w *writerAggregator) WriteLine(s string) {
|
||||
w.WriteString(s)
|
||||
w.WriteString("\r\n")
|
||||
}
|
||||
|
||||
func (w *writerAggregator) WriteLinef(format string, args ...any) {
|
||||
w.WriteStringf(format+"\r\n", args...)
|
||||
}
|
||||
|
||||
type sourceMapSpanWithDecodeErrors struct {
|
||||
sourceMapSpan *sourcemap.Mapping
|
||||
decodeErrors []string
|
||||
}
|
||||
|
||||
type decodedMapping struct {
|
||||
sourceMapSpan *sourcemap.Mapping
|
||||
error error
|
||||
}
|
||||
|
||||
type sourceMapDecoder struct {
|
||||
sourceMapMappings string
|
||||
mappings *sourcemap.MappingsDecoder
|
||||
}
|
||||
|
||||
func newSourceMapDecoder(sourceMap *sourcemap.RawSourceMap) *sourceMapDecoder {
|
||||
return &sourceMapDecoder{
|
||||
sourceMapMappings: sourceMap.Mappings,
|
||||
mappings: sourcemap.DecodeMappings(sourceMap.Mappings),
|
||||
}
|
||||
}
|
||||
|
||||
func (d *sourceMapDecoder) decodeNextEncodedSourceMapSpan() *decodedMapping {
|
||||
value, done := d.mappings.Next()
|
||||
if done {
|
||||
mapping := &decodedMapping{
|
||||
error: d.mappings.Error(),
|
||||
sourceMapSpan: d.mappings.State(),
|
||||
}
|
||||
if mapping.error == nil {
|
||||
mapping.error = errors.New("No encoded entry found")
|
||||
}
|
||||
return mapping
|
||||
}
|
||||
return &decodedMapping{sourceMapSpan: value}
|
||||
}
|
||||
|
||||
func (d *sourceMapDecoder) hasCompletedDecoding() bool {
|
||||
return d.mappings.Pos() == len(d.sourceMapMappings)
|
||||
}
|
||||
|
||||
func (d *sourceMapDecoder) getRemainingDecodeString() string {
|
||||
return d.sourceMapMappings[d.mappings.Pos():]
|
||||
}
|
||||
|
||||
type sourceMapSpanWriter struct {
|
||||
sourceMapRecorder *writerAggregator
|
||||
sourceMapSources []string
|
||||
sourceMapNames []string
|
||||
jsFile *TestFile
|
||||
jsLineMap []core.TextPos
|
||||
tsCode string
|
||||
tsLineMap []core.TextPos
|
||||
spansOnSingleLine []sourceMapSpanWithDecodeErrors
|
||||
prevWrittenSourcePos int
|
||||
nextJsLineToWrite int
|
||||
spanMarkerContinues bool
|
||||
sourceMapDecoder *sourceMapDecoder
|
||||
}
|
||||
|
||||
func newSourceMapSpanWriter(sourceMapRecorder *writerAggregator, sourceMap *sourcemap.RawSourceMap, jsFile *TestFile) *sourceMapSpanWriter {
|
||||
writer := &sourceMapSpanWriter{
|
||||
sourceMapRecorder: sourceMapRecorder,
|
||||
sourceMapSources: sourceMap.Sources,
|
||||
sourceMapNames: sourceMap.Names,
|
||||
jsFile: jsFile,
|
||||
jsLineMap: core.ComputeECMALineStarts(jsFile.Content),
|
||||
spansOnSingleLine: make([]sourceMapSpanWithDecodeErrors, 0),
|
||||
prevWrittenSourcePos: 0,
|
||||
nextJsLineToWrite: 0,
|
||||
spanMarkerContinues: false,
|
||||
sourceMapDecoder: newSourceMapDecoder(sourceMap),
|
||||
}
|
||||
|
||||
sourceMapRecorder.WriteLine("===================================================================")
|
||||
sourceMapRecorder.WriteLinef("JsFile: %s", sourceMap.File)
|
||||
sourceMapRecorder.WriteLinef("mapUrl: %s", sourcemap.TryGetSourceMappingURL(sourcemap.CreateECMALineInfo(jsFile.Content, writer.jsLineMap)))
|
||||
sourceMapRecorder.WriteLinef("sourceRoot: %s", sourceMap.SourceRoot)
|
||||
sourceMapRecorder.WriteLinef("sources: %s", strings.Join(sourceMap.Sources, ","))
|
||||
if len(sourceMap.SourcesContent) > 0 {
|
||||
content, err := json.Marshal(sourceMap.SourcesContent)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
sourceMapRecorder.WriteLinef("sourcesContent: %s", content)
|
||||
}
|
||||
sourceMapRecorder.WriteLine("===================================================================")
|
||||
return writer
|
||||
}
|
||||
|
||||
func (w *sourceMapSpanWriter) getSourceMapSpanString(mapEntry *sourcemap.Mapping, getAbsentNameIndex bool) string {
|
||||
var mapString writerAggregator
|
||||
mapString.WriteStringf("Emitted(%d, %d)", mapEntry.GeneratedLine+1, mapEntry.GeneratedCharacter+1)
|
||||
if mapEntry.IsSourceMapping() {
|
||||
mapString.WriteStringf(" Source(%d, %d) + SourceIndex(%d)", mapEntry.SourceLine+1, mapEntry.SourceCharacter+1, mapEntry.SourceIndex)
|
||||
if mapEntry.NameIndex >= 0 && int(mapEntry.NameIndex) < len(w.sourceMapNames) {
|
||||
mapString.WriteStringf(" name (%s)", w.sourceMapNames[mapEntry.NameIndex])
|
||||
} else {
|
||||
if mapEntry.NameIndex != sourcemap.MissingName || getAbsentNameIndex {
|
||||
mapString.WriteStringf(" nameIndex (%d)", mapEntry.NameIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
return mapString.String()
|
||||
}
|
||||
|
||||
func (w *sourceMapSpanWriter) recordSourceMapSpan(sourceMapSpan *sourcemap.Mapping) {
|
||||
// verify the decoded span is same as the new span
|
||||
decodeResult := w.sourceMapDecoder.decodeNextEncodedSourceMapSpan()
|
||||
var decodeErrors []string
|
||||
if decodeResult.error != nil || !decodeResult.sourceMapSpan.Equals(sourceMapSpan) {
|
||||
if decodeResult.error != nil {
|
||||
decodeErrors = []string{"!!^^ !!^^ There was decoding error in the sourcemap at this location: " + decodeResult.error.Error()}
|
||||
} else {
|
||||
decodeErrors = []string{"!!^^ !!^^ The decoded span from sourcemap's mapping entry does not match what was encoded for this span:"}
|
||||
}
|
||||
decodeErrors = append(
|
||||
decodeErrors,
|
||||
"!!^^ !!^^ Decoded span from sourcemap's mappings entry: "+
|
||||
w.getSourceMapSpanString(decodeResult.sourceMapSpan, true /*getAbsentNameIndex*/)+
|
||||
" Span encoded by the emitter:"+
|
||||
w.getSourceMapSpanString(sourceMapSpan, true /*getAbsentNameIndex*/),
|
||||
)
|
||||
}
|
||||
|
||||
if len(w.spansOnSingleLine) > 0 && w.spansOnSingleLine[0].sourceMapSpan.GeneratedLine != sourceMapSpan.GeneratedLine {
|
||||
// On different line from the one that we have been recording till now,
|
||||
w.writeRecordedSpans()
|
||||
w.spansOnSingleLine = nil
|
||||
}
|
||||
w.spansOnSingleLine = append(w.spansOnSingleLine, sourceMapSpanWithDecodeErrors{
|
||||
sourceMapSpan: sourceMapSpan,
|
||||
decodeErrors: decodeErrors,
|
||||
})
|
||||
}
|
||||
|
||||
func (w *sourceMapSpanWriter) recordNewSourceFileSpan(sourceMapSpan *sourcemap.Mapping, newSourceFileCode string) {
|
||||
continuesLine := false
|
||||
if len(w.spansOnSingleLine) > 0 && int(w.spansOnSingleLine[0].sourceMapSpan.GeneratedCharacter) == sourceMapSpan.GeneratedLine { // !!! char == line seems like a bug in Strada?
|
||||
w.writeRecordedSpans()
|
||||
w.spansOnSingleLine = nil
|
||||
w.nextJsLineToWrite-- // walk back one line to reprint the line
|
||||
continuesLine = true
|
||||
}
|
||||
|
||||
w.recordSourceMapSpan(sourceMapSpan)
|
||||
|
||||
if len(w.spansOnSingleLine) != 1 {
|
||||
panic("expected a single span")
|
||||
}
|
||||
|
||||
w.sourceMapRecorder.WriteLine("-------------------------------------------------------------------")
|
||||
if continuesLine {
|
||||
w.sourceMapRecorder.WriteLinef("emittedFile:%s (%d, %d)", w.jsFile.UnitName, sourceMapSpan.GeneratedLine+1, sourceMapSpan.GeneratedCharacter+1)
|
||||
} else {
|
||||
w.sourceMapRecorder.WriteLinef("emittedFile:%s", w.jsFile.UnitName)
|
||||
}
|
||||
w.sourceMapRecorder.WriteLinef("sourceFile:%s", w.sourceMapSources[w.spansOnSingleLine[0].sourceMapSpan.SourceIndex])
|
||||
w.sourceMapRecorder.WriteLine("-------------------------------------------------------------------")
|
||||
|
||||
w.tsLineMap = core.ComputeECMALineStarts(newSourceFileCode)
|
||||
w.tsCode = newSourceFileCode
|
||||
w.prevWrittenSourcePos = 0
|
||||
}
|
||||
|
||||
func (w *sourceMapSpanWriter) close() {
|
||||
// Write the lines pending on the single line
|
||||
w.writeRecordedSpans()
|
||||
|
||||
if !w.sourceMapDecoder.hasCompletedDecoding() {
|
||||
w.sourceMapRecorder.WriteLine("!!!! **** There are more source map entries in the sourceMap's mapping than what was encoded")
|
||||
w.sourceMapRecorder.WriteLinef("!!!! **** Remaining decoded string: %s", w.sourceMapDecoder.getRemainingDecodeString())
|
||||
}
|
||||
|
||||
// write remaining js lines
|
||||
w.writeJsFileLines(len(w.jsLineMap))
|
||||
}
|
||||
|
||||
func (w *sourceMapSpanWriter) getTextOfLine(line int, lineMap []core.TextPos, code string) string {
|
||||
startPos := lineMap[line]
|
||||
var endPos core.TextPos
|
||||
if line+1 < len(lineMap) {
|
||||
endPos = lineMap[line+1]
|
||||
} else {
|
||||
endPos = core.TextPos(len(code))
|
||||
}
|
||||
text := code[startPos:endPos]
|
||||
if line == 0 {
|
||||
return stringutil.RemoveByteOrderMark(text)
|
||||
}
|
||||
// return line == 0 ? Utils.removeByteOrderMark(text) : text;
|
||||
return text
|
||||
}
|
||||
|
||||
func (w *sourceMapSpanWriter) writeJsFileLines(endJsLine int) {
|
||||
for ; w.nextJsLineToWrite < endJsLine; w.nextJsLineToWrite++ {
|
||||
w.sourceMapRecorder.WriteStringf(">>>%s", w.getTextOfLine(w.nextJsLineToWrite, w.jsLineMap, w.jsFile.Content))
|
||||
}
|
||||
}
|
||||
|
||||
func (w *sourceMapSpanWriter) writeRecordedSpans() {
|
||||
recordedSpanWriter := recordedSpanWriter{w: w}
|
||||
recordedSpanWriter.writeRecordedSpans()
|
||||
}
|
||||
|
||||
type recordedSpanWriter struct {
|
||||
markerIds []string
|
||||
prevEmittedCol int
|
||||
w *sourceMapSpanWriter
|
||||
}
|
||||
|
||||
func (sw *recordedSpanWriter) getMarkerId(markerIndex int) string {
|
||||
markerId := ""
|
||||
if sw.w.spanMarkerContinues {
|
||||
if markerIndex != 0 {
|
||||
panic("expected markerIndex to be 0")
|
||||
}
|
||||
markerId = "1->"
|
||||
} else {
|
||||
markerId = strconv.Itoa(markerIndex + 1)
|
||||
if len(markerId) < 2 {
|
||||
markerId += " "
|
||||
}
|
||||
markerId += ">"
|
||||
}
|
||||
return markerId
|
||||
}
|
||||
|
||||
func (sw *recordedSpanWriter) iterateSpans(fn func(currentSpan *sourceMapSpanWithDecodeErrors, index int)) {
|
||||
sw.prevEmittedCol = 0
|
||||
for i := range len(sw.w.spansOnSingleLine) {
|
||||
fn(&sw.w.spansOnSingleLine[i], i)
|
||||
sw.prevEmittedCol = int(sw.w.spansOnSingleLine[i].sourceMapSpan.GeneratedCharacter)
|
||||
}
|
||||
}
|
||||
|
||||
func (sw *recordedSpanWriter) writeSourceMapIndent(indentLength int, indentPrefix string) {
|
||||
sw.w.sourceMapRecorder.WriteString(indentPrefix)
|
||||
for range indentLength {
|
||||
sw.w.sourceMapRecorder.WriteString(" ")
|
||||
}
|
||||
}
|
||||
|
||||
func (sw *recordedSpanWriter) writeSourceMapMarker(currentSpan *sourceMapSpanWithDecodeErrors, index int) {
|
||||
sw.writeSourceMapMarkerEx(currentSpan, index, int(currentSpan.sourceMapSpan.GeneratedCharacter), false /*endContinues*/)
|
||||
}
|
||||
|
||||
func (sw *recordedSpanWriter) writeSourceMapMarkerEx(currentSpan *sourceMapSpanWithDecodeErrors, index int, endColumn int, endContinues bool) {
|
||||
markerId := sw.getMarkerId(index)
|
||||
sw.markerIds = append(sw.markerIds, markerId)
|
||||
sw.writeSourceMapIndent(sw.prevEmittedCol, markerId)
|
||||
for i := sw.prevEmittedCol; i < endColumn; i++ {
|
||||
sw.w.sourceMapRecorder.WriteString("^")
|
||||
}
|
||||
if endContinues {
|
||||
sw.w.sourceMapRecorder.WriteString("->")
|
||||
}
|
||||
sw.w.sourceMapRecorder.WriteLine("")
|
||||
sw.w.spanMarkerContinues = endContinues
|
||||
}
|
||||
|
||||
func (sw *recordedSpanWriter) writeSourceMapSourceText(currentSpan *sourceMapSpanWithDecodeErrors, index int) {
|
||||
// Convert UTF-16 character offset from the source map to a byte position.
|
||||
sourcePos := scanner.ComputePositionOfLineAndUTF16Character(
|
||||
sw.w.tsLineMap,
|
||||
currentSpan.sourceMapSpan.SourceLine,
|
||||
currentSpan.sourceMapSpan.SourceCharacter,
|
||||
sw.w.tsCode,
|
||||
true, /*allowEdits*/
|
||||
)
|
||||
var sourceText string
|
||||
if sw.w.prevWrittenSourcePos < sourcePos {
|
||||
// Position that goes forward, get text
|
||||
sourceText = sw.w.tsCode[sw.w.prevWrittenSourcePos:sourcePos]
|
||||
}
|
||||
|
||||
// If there are decode errors, write
|
||||
for _, decodeError := range currentSpan.decodeErrors {
|
||||
sw.writeSourceMapIndent(sw.prevEmittedCol, sw.markerIds[index])
|
||||
sw.w.sourceMapRecorder.WriteLine(decodeError)
|
||||
}
|
||||
|
||||
tsCodeLineMap := core.ComputeECMALineStarts(sourceText)
|
||||
for i := range tsCodeLineMap {
|
||||
if i == 0 {
|
||||
sw.writeSourceMapIndent(sw.prevEmittedCol, sw.markerIds[index])
|
||||
} else {
|
||||
sw.writeSourceMapIndent(sw.prevEmittedCol, " >")
|
||||
}
|
||||
sw.w.sourceMapRecorder.WriteString(sw.w.getTextOfLine(i, tsCodeLineMap, sourceText))
|
||||
if i == len(tsCodeLineMap)-1 {
|
||||
sw.w.sourceMapRecorder.WriteLine("")
|
||||
}
|
||||
}
|
||||
|
||||
sw.w.prevWrittenSourcePos = sourcePos
|
||||
}
|
||||
|
||||
func (sw *recordedSpanWriter) writeSpanDetails(currentSpan *sourceMapSpanWithDecodeErrors, index int) {
|
||||
sw.w.sourceMapRecorder.WriteLinef("%s%s", sw.markerIds[index], sw.w.getSourceMapSpanString(currentSpan.sourceMapSpan, false /*getAbsentNameIndex*/))
|
||||
}
|
||||
|
||||
func (sw *recordedSpanWriter) writeRecordedSpans() {
|
||||
w := sw.w
|
||||
writeSourceMapMarker := sw.writeSourceMapMarker
|
||||
writeSourceMapSourceText := sw.writeSourceMapSourceText
|
||||
writeSpanDetails := sw.writeSpanDetails
|
||||
|
||||
if len(w.spansOnSingleLine) > 0 {
|
||||
currentJsLine := w.spansOnSingleLine[0].sourceMapSpan.GeneratedLine
|
||||
|
||||
// Write js line
|
||||
w.writeJsFileLines(currentJsLine + 1)
|
||||
|
||||
// Emit markers
|
||||
sw.iterateSpans(writeSourceMapMarker)
|
||||
|
||||
jsFileText := w.getTextOfLine(currentJsLine+1, w.jsLineMap, w.jsFile.Content) // TODO: Strada is wrong here, we should be looking at `currentJsLine`, not `currentJsLine+1`
|
||||
if sw.prevEmittedCol < len(jsFileText)-1 {
|
||||
// There is remaining text on this line that will be part of next source span so write marker that continues
|
||||
sw.writeSourceMapMarkerEx(nil /*currentSpan*/, len(w.spansOnSingleLine), len(jsFileText)-1 /*endColumn*/, true /*endContinues*/)
|
||||
}
|
||||
|
||||
// Emit Source text
|
||||
sw.iterateSpans(writeSourceMapSourceText)
|
||||
|
||||
// Emit column number etc
|
||||
sw.iterateSpans(writeSpanDetails)
|
||||
|
||||
w.sourceMapRecorder.WriteLine("---")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user