Files
huishou/node_modules/@dcloudio/uni-stacktracey/lib/source-map/lib/source-node.js
T
2026-07-27 14:07:26 +08:00

431 lines
13 KiB
JavaScript

/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
const SourceMapGenerator = require('./source-map-generator').SourceMapGenerator
const util = require('./util')
// Matches a Windows-style `\r\n` newline or a `\n` newline used by all other
// operating systems these days (capturing the result).
const REGEX_NEWLINE = /(\r?\n)/
// Newline character code for charCodeAt() comparisons
const NEWLINE_CODE = 10
// Private symbol for identifying `SourceNode`s when multiple versions of
// the source-map library are loaded. This MUST NOT CHANGE across
// versions!
const isSourceNode = '$$$isSourceNode$$$'
/**
* SourceNodes provide a way to abstract over interpolating/concatenating
* snippets of generated JavaScript source code while maintaining the line and
* column information associated with the original source code.
*
* @param aLine The original line number.
* @param aColumn The original column number.
* @param aSource The original source's filename.
* @param aChunks Optional. An array of strings which are snippets of
* generated JS, or other SourceNodes.
* @param aName The original identifier.
*/
class SourceNode {
constructor(aLine, aColumn, aSource, aChunks, aName) {
this.children = []
this.sourceContents = {}
this.line = aLine == null ? null : aLine
this.column = aColumn == null ? null : aColumn
this.source = aSource == null ? null : aSource
this.name = aName == null ? null : aName
this[isSourceNode] = true
if (aChunks != null) this.add(aChunks)
}
/**
* Creates a SourceNode from generated code and a SourceMapConsumer.
*
* @param aGeneratedCode The generated code
* @param aSourceMapConsumer The SourceMap for the generated code
* @param aRelativePath Optional. The path that relative sources in the
* SourceMapConsumer should be relative to.
*/
static fromStringWithSourceMap(
aGeneratedCode,
aSourceMapConsumer,
aRelativePath
) {
// The SourceNode we want to fill with the generated code
// and the SourceMap
const node = new SourceNode()
// All even indices of this array are one line of the generated code,
// while all odd indices are the newlines between two adjacent lines
// (since `REGEX_NEWLINE` captures its match).
// Processed fragments are accessed by calling `shiftNextLine`.
const remainingLines = aGeneratedCode.split(REGEX_NEWLINE)
let remainingLinesIndex = 0
const shiftNextLine = function () {
const lineContents = getNextLine()
// The last line of a file might not have a newline.
const newLine = getNextLine() || ''
return lineContents + newLine
function getNextLine() {
return remainingLinesIndex < remainingLines.length
? remainingLines[remainingLinesIndex++]
: undefined
}
}
// We need to remember the position of "remainingLines"
let lastGeneratedLine = 1,
lastGeneratedColumn = 0
// The generate SourceNodes we need a code range.
// To extract it current and last mapping is used.
// Here we store the last mapping.
let lastMapping = null
let nextLine
aSourceMapConsumer.eachMapping(function (mapping) {
if (lastMapping !== null) {
// We add the code from "lastMapping" to "mapping":
// First check if there is a new line in between.
if (lastGeneratedLine < mapping.generatedLine) {
// Associate first line with "lastMapping"
addMappingWithCode(lastMapping, shiftNextLine())
lastGeneratedLine++
lastGeneratedColumn = 0
// The remaining code is added without mapping
} else {
// There is no new line in between.
// Associate the code between "lastGeneratedColumn" and
// "mapping.generatedColumn" with "lastMapping"
nextLine = remainingLines[remainingLinesIndex] || ''
const code = nextLine.substr(
0,
mapping.generatedColumn - lastGeneratedColumn
)
remainingLines[remainingLinesIndex] = nextLine.substr(
mapping.generatedColumn - lastGeneratedColumn
)
lastGeneratedColumn = mapping.generatedColumn
addMappingWithCode(lastMapping, code)
// No more remaining code, continue
lastMapping = mapping
return
}
}
// We add the generated code until the first mapping
// to the SourceNode without any mapping.
// Each line is added as separate string.
while (lastGeneratedLine < mapping.generatedLine) {
node.add(shiftNextLine())
lastGeneratedLine++
}
if (lastGeneratedColumn < mapping.generatedColumn) {
nextLine = remainingLines[remainingLinesIndex] || ''
node.add(nextLine.substr(0, mapping.generatedColumn))
remainingLines[remainingLinesIndex] = nextLine.substr(
mapping.generatedColumn
)
lastGeneratedColumn = mapping.generatedColumn
}
lastMapping = mapping
}, this)
// We have processed all mappings.
if (remainingLinesIndex < remainingLines.length) {
if (lastMapping) {
// Associate the remaining code in the current line with "lastMapping"
addMappingWithCode(lastMapping, shiftNextLine())
}
// and add the remaining lines without any mapping
node.add(remainingLines.splice(remainingLinesIndex).join(''))
}
// Copy sourcesContent into SourceNode
aSourceMapConsumer.sources.forEach(function (sourceFile) {
const content = aSourceMapConsumer.sourceContentFor(sourceFile)
if (content != null) {
if (aRelativePath != null) {
sourceFile = util.join(aRelativePath, sourceFile)
}
node.setSourceContent(sourceFile, content)
}
})
return node
function addMappingWithCode(mapping, code) {
if (mapping === null || mapping.source === undefined) {
node.add(code)
} else {
const source = aRelativePath
? util.join(aRelativePath, mapping.source)
: mapping.source
node.add(
new SourceNode(
mapping.originalLine,
mapping.originalColumn,
source,
code,
mapping.name
)
)
}
}
}
/**
* Add a chunk of generated JS to this source node.
*
* @param aChunk A string snippet of generated JS code, another instance of
* SourceNode, or an array where each member is one of those things.
*/
add(aChunk) {
if (Array.isArray(aChunk)) {
aChunk.forEach(function (chunk) {
this.add(chunk)
}, this)
} else if (aChunk[isSourceNode] || typeof aChunk === 'string') {
if (aChunk) {
this.children.push(aChunk)
}
} else {
throw new TypeError(
'Expected a SourceNode, string, or an array of SourceNodes and strings. Got ' +
aChunk
)
}
return this
}
/**
* Add a chunk of generated JS to the beginning of this source node.
*
* @param aChunk A string snippet of generated JS code, another instance of
* SourceNode, or an array where each member is one of those things.
*/
prepend(aChunk) {
if (Array.isArray(aChunk)) {
for (let i = aChunk.length - 1; i >= 0; i--) {
this.prepend(aChunk[i])
}
} else if (aChunk[isSourceNode] || typeof aChunk === 'string') {
this.children.unshift(aChunk)
} else {
throw new TypeError(
'Expected a SourceNode, string, or an array of SourceNodes and strings. Got ' +
aChunk
)
}
return this
}
/**
* Walk over the tree of JS snippets in this node and its children. The
* walking function is called once for each snippet of JS and is passed that
* snippet and the its original associated source's line/column location.
*
* @param aFn The traversal function.
*/
walk(aFn) {
let chunk
for (let i = 0, len = this.children.length; i < len; i++) {
chunk = this.children[i]
if (chunk[isSourceNode]) {
chunk.walk(aFn)
} else if (chunk !== '') {
aFn(chunk, {
source: this.source,
line: this.line,
column: this.column,
name: this.name,
})
}
}
}
/**
* Like `String.prototype.join` except for SourceNodes. Inserts `aStr` between
* each of `this.children`.
*
* @param aSep The separator.
*/
join(aSep) {
let newChildren
let i
const len = this.children.length
if (len > 0) {
newChildren = []
for (i = 0; i < len - 1; i++) {
newChildren.push(this.children[i])
newChildren.push(aSep)
}
newChildren.push(this.children[i])
this.children = newChildren
}
return this
}
/**
* Call String.prototype.replace on the very right-most source snippet. Useful
* for trimming whitespace from the end of a source node, etc.
*
* @param aPattern The pattern to replace.
* @param aReplacement The thing to replace the pattern with.
*/
replaceRight(aPattern, aReplacement) {
const lastChild = this.children[this.children.length - 1]
if (lastChild[isSourceNode]) {
lastChild.replaceRight(aPattern, aReplacement)
} else if (typeof lastChild === 'string') {
this.children[this.children.length - 1] = lastChild.replace(
aPattern,
aReplacement
)
} else {
this.children.push(''.replace(aPattern, aReplacement))
}
return this
}
/**
* Set the source content for a source file. This will be added to the SourceMapGenerator
* in the sourcesContent field.
*
* @param aSourceFile The filename of the source file
* @param aSourceContent The content of the source file
*/
setSourceContent(aSourceFile, aSourceContent) {
this.sourceContents[util.toSetString(aSourceFile)] = aSourceContent
}
/**
* Walk over the tree of SourceNodes. The walking function is called for each
* source file content and is passed the filename and source content.
*
* @param aFn The traversal function.
*/
walkSourceContents(aFn) {
for (let i = 0, len = this.children.length; i < len; i++) {
if (this.children[i][isSourceNode]) {
this.children[i].walkSourceContents(aFn)
}
}
const sources = Object.keys(this.sourceContents)
for (let i = 0, len = sources.length; i < len; i++) {
aFn(util.fromSetString(sources[i]), this.sourceContents[sources[i]])
}
}
/**
* Return the string representation of this source node. Walks over the tree
* and concatenates all the various snippets together to one string.
*/
toString() {
let str = ''
this.walk(function (chunk) {
str += chunk
})
return str
}
/**
* Returns the string representation of this source node along with a source
* map.
*/
toStringWithSourceMap(aArgs) {
const generated = {
code: '',
line: 1,
column: 0,
}
const map = new SourceMapGenerator(aArgs)
let sourceMappingActive = false
let lastOriginalSource = null
let lastOriginalLine = null
let lastOriginalColumn = null
let lastOriginalName = null
this.walk(function (chunk, original) {
generated.code += chunk
if (
original.source !== null &&
original.line !== null &&
original.column !== null
) {
if (
lastOriginalSource !== original.source ||
lastOriginalLine !== original.line ||
lastOriginalColumn !== original.column ||
lastOriginalName !== original.name
) {
map.addMapping({
source: original.source,
original: {
line: original.line,
column: original.column,
},
generated: {
line: generated.line,
column: generated.column,
},
name: original.name,
})
}
lastOriginalSource = original.source
lastOriginalLine = original.line
lastOriginalColumn = original.column
lastOriginalName = original.name
sourceMappingActive = true
} else if (sourceMappingActive) {
map.addMapping({
generated: {
line: generated.line,
column: generated.column,
},
})
lastOriginalSource = null
sourceMappingActive = false
}
for (let idx = 0, length = chunk.length; idx < length; idx++) {
if (chunk.charCodeAt(idx) === NEWLINE_CODE) {
generated.line++
generated.column = 0
// Mappings end at eol
if (idx + 1 === length) {
lastOriginalSource = null
sourceMappingActive = false
} else if (sourceMappingActive) {
map.addMapping({
source: original.source,
original: {
line: original.line,
column: original.column,
},
generated: {
line: generated.line,
column: generated.column,
},
name: original.name,
})
}
} else {
generated.column++
}
}
})
this.walkSourceContents(function (sourceFile, sourceContent) {
map.setSourceContent(sourceFile, sourceContent)
})
return { code: generated.code, map }
}
}
exports.SourceNode = SourceNode