You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
319 lines
7.8 KiB
319 lines
7.8 KiB
/* |
|
MIT License http://www.opensource.org/licenses/mit-license.php |
|
Author Tobias Koppers @sokra |
|
*/ |
|
"use strict"; |
|
|
|
const Source = require("./Source"); |
|
const RawSource = require("./RawSource"); |
|
const streamChunks = require("./helpers/streamChunks"); |
|
const { getMap, getSourceAndMap } = require("./helpers/getFromStreamChunks"); |
|
|
|
const stringsAsRawSources = new WeakSet(); |
|
|
|
class ConcatSource extends Source { |
|
constructor() { |
|
super(); |
|
this._children = []; |
|
for (let i = 0; i < arguments.length; i++) { |
|
const item = arguments[i]; |
|
if (item instanceof ConcatSource) { |
|
for (const child of item._children) { |
|
this._children.push(child); |
|
} |
|
} else { |
|
this._children.push(item); |
|
} |
|
} |
|
this._isOptimized = arguments.length === 0; |
|
} |
|
|
|
getChildren() { |
|
if (!this._isOptimized) this._optimize(); |
|
return this._children; |
|
} |
|
|
|
add(item) { |
|
if (item instanceof ConcatSource) { |
|
for (const child of item._children) { |
|
this._children.push(child); |
|
} |
|
} else { |
|
this._children.push(item); |
|
} |
|
this._isOptimized = false; |
|
} |
|
|
|
addAllSkipOptimizing(items) { |
|
for (const item of items) { |
|
this._children.push(item); |
|
} |
|
} |
|
|
|
buffer() { |
|
if (!this._isOptimized) this._optimize(); |
|
const buffers = []; |
|
for (const child of this._children) { |
|
if (typeof child.buffer === "function") { |
|
buffers.push(child.buffer()); |
|
} else { |
|
const bufferOrString = child.source(); |
|
if (Buffer.isBuffer(bufferOrString)) { |
|
buffers.push(bufferOrString); |
|
} else { |
|
// This will not happen |
|
buffers.push(Buffer.from(bufferOrString, "utf-8")); |
|
} |
|
} |
|
} |
|
return Buffer.concat(buffers); |
|
} |
|
|
|
source() { |
|
if (!this._isOptimized) this._optimize(); |
|
let source = ""; |
|
for (const child of this._children) { |
|
source += child.source(); |
|
} |
|
return source; |
|
} |
|
|
|
size() { |
|
if (!this._isOptimized) this._optimize(); |
|
let size = 0; |
|
for (const child of this._children) { |
|
size += child.size(); |
|
} |
|
return size; |
|
} |
|
|
|
map(options) { |
|
return getMap(this, options); |
|
} |
|
|
|
sourceAndMap(options) { |
|
return getSourceAndMap(this, options); |
|
} |
|
|
|
streamChunks(options, onChunk, onSource, onName) { |
|
if (!this._isOptimized) this._optimize(); |
|
if (this._children.length === 1) |
|
return this._children[0].streamChunks(options, onChunk, onSource, onName); |
|
let currentLineOffset = 0; |
|
let currentColumnOffset = 0; |
|
let sourceMapping = new Map(); |
|
let nameMapping = new Map(); |
|
const finalSource = !!(options && options.finalSource); |
|
let code = ""; |
|
let needToCloseMapping = false; |
|
for (const item of this._children) { |
|
const sourceIndexMapping = []; |
|
const nameIndexMapping = []; |
|
let lastMappingLine = 0; |
|
const { generatedLine, generatedColumn, source } = streamChunks( |
|
item, |
|
options, |
|
// eslint-disable-next-line no-loop-func |
|
( |
|
chunk, |
|
generatedLine, |
|
generatedColumn, |
|
sourceIndex, |
|
originalLine, |
|
originalColumn, |
|
nameIndex |
|
) => { |
|
const line = generatedLine + currentLineOffset; |
|
const column = |
|
generatedLine === 1 |
|
? generatedColumn + currentColumnOffset |
|
: generatedColumn; |
|
if (needToCloseMapping) { |
|
if (generatedLine !== 1 || generatedColumn !== 0) { |
|
onChunk( |
|
undefined, |
|
currentLineOffset + 1, |
|
currentColumnOffset, |
|
-1, |
|
-1, |
|
-1, |
|
-1 |
|
); |
|
} |
|
needToCloseMapping = false; |
|
} |
|
const resultSourceIndex = |
|
sourceIndex < 0 || sourceIndex >= sourceIndexMapping.length |
|
? -1 |
|
: sourceIndexMapping[sourceIndex]; |
|
const resultNameIndex = |
|
nameIndex < 0 || nameIndex >= nameIndexMapping.length |
|
? -1 |
|
: nameIndexMapping[nameIndex]; |
|
lastMappingLine = resultSourceIndex < 0 ? 0 : generatedLine; |
|
if (finalSource) { |
|
if (chunk !== undefined) code += chunk; |
|
if (resultSourceIndex >= 0) { |
|
onChunk( |
|
undefined, |
|
line, |
|
column, |
|
resultSourceIndex, |
|
originalLine, |
|
originalColumn, |
|
resultNameIndex |
|
); |
|
} |
|
} else { |
|
if (resultSourceIndex < 0) { |
|
onChunk(chunk, line, column, -1, -1, -1, -1); |
|
} else { |
|
onChunk( |
|
chunk, |
|
line, |
|
column, |
|
resultSourceIndex, |
|
originalLine, |
|
originalColumn, |
|
resultNameIndex |
|
); |
|
} |
|
} |
|
}, |
|
(i, source, sourceContent) => { |
|
let globalIndex = sourceMapping.get(source); |
|
if (globalIndex === undefined) { |
|
sourceMapping.set(source, (globalIndex = sourceMapping.size)); |
|
onSource(globalIndex, source, sourceContent); |
|
} |
|
sourceIndexMapping[i] = globalIndex; |
|
}, |
|
(i, name) => { |
|
let globalIndex = nameMapping.get(name); |
|
if (globalIndex === undefined) { |
|
nameMapping.set(name, (globalIndex = nameMapping.size)); |
|
onName(globalIndex, name); |
|
} |
|
nameIndexMapping[i] = globalIndex; |
|
} |
|
); |
|
if (source !== undefined) code += source; |
|
if (needToCloseMapping) { |
|
if (generatedLine !== 1 || generatedColumn !== 0) { |
|
onChunk( |
|
undefined, |
|
currentLineOffset + 1, |
|
currentColumnOffset, |
|
-1, |
|
-1, |
|
-1, |
|
-1 |
|
); |
|
needToCloseMapping = false; |
|
} |
|
} |
|
if (generatedLine > 1) { |
|
currentColumnOffset = generatedColumn; |
|
} else { |
|
currentColumnOffset += generatedColumn; |
|
} |
|
needToCloseMapping = |
|
needToCloseMapping || |
|
(finalSource && lastMappingLine === generatedLine); |
|
currentLineOffset += generatedLine - 1; |
|
} |
|
return { |
|
generatedLine: currentLineOffset + 1, |
|
generatedColumn: currentColumnOffset, |
|
source: finalSource ? code : undefined |
|
}; |
|
} |
|
|
|
updateHash(hash) { |
|
if (!this._isOptimized) this._optimize(); |
|
hash.update("ConcatSource"); |
|
for (const item of this._children) { |
|
item.updateHash(hash); |
|
} |
|
} |
|
|
|
_optimize() { |
|
const newChildren = []; |
|
let currentString = undefined; |
|
let currentRawSources = undefined; |
|
const addStringToRawSources = string => { |
|
if (currentRawSources === undefined) { |
|
currentRawSources = string; |
|
} else if (Array.isArray(currentRawSources)) { |
|
currentRawSources.push(string); |
|
} else { |
|
currentRawSources = [ |
|
typeof currentRawSources === "string" |
|
? currentRawSources |
|
: currentRawSources.source(), |
|
string |
|
]; |
|
} |
|
}; |
|
const addSourceToRawSources = source => { |
|
if (currentRawSources === undefined) { |
|
currentRawSources = source; |
|
} else if (Array.isArray(currentRawSources)) { |
|
currentRawSources.push(source.source()); |
|
} else { |
|
currentRawSources = [ |
|
typeof currentRawSources === "string" |
|
? currentRawSources |
|
: currentRawSources.source(), |
|
source.source() |
|
]; |
|
} |
|
}; |
|
const mergeRawSources = () => { |
|
if (Array.isArray(currentRawSources)) { |
|
const rawSource = new RawSource(currentRawSources.join("")); |
|
stringsAsRawSources.add(rawSource); |
|
newChildren.push(rawSource); |
|
} else if (typeof currentRawSources === "string") { |
|
const rawSource = new RawSource(currentRawSources); |
|
stringsAsRawSources.add(rawSource); |
|
newChildren.push(rawSource); |
|
} else { |
|
newChildren.push(currentRawSources); |
|
} |
|
}; |
|
for (const child of this._children) { |
|
if (typeof child === "string") { |
|
if (currentString === undefined) { |
|
currentString = child; |
|
} else { |
|
currentString += child; |
|
} |
|
} else { |
|
if (currentString !== undefined) { |
|
addStringToRawSources(currentString); |
|
currentString = undefined; |
|
} |
|
if (stringsAsRawSources.has(child)) { |
|
addSourceToRawSources(child); |
|
} else { |
|
if (currentRawSources !== undefined) { |
|
mergeRawSources(); |
|
currentRawSources = undefined; |
|
} |
|
newChildren.push(child); |
|
} |
|
} |
|
} |
|
if (currentString !== undefined) { |
|
addStringToRawSources(currentString); |
|
} |
|
if (currentRawSources !== undefined) { |
|
mergeRawSources(); |
|
} |
|
this._children = newChildren; |
|
this._isOptimized = true; |
|
} |
|
} |
|
|
|
module.exports = ConcatSource;
|
|
|