2020-05-26 23:08:32 +02:00
|
|
|
/**
|
|
|
|
|
* Copyright (c) Microsoft Corporation.
|
|
|
|
|
*
|
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
|
*
|
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
*
|
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
|
* limitations under the License.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import * as fs from 'fs';
|
|
|
|
|
import * as util from 'util';
|
2020-05-27 23:26:44 +02:00
|
|
|
import { getCallerFilePath } from './stackTrace';
|
2020-05-26 23:08:32 +02:00
|
|
|
|
|
|
|
|
type Position = {
|
|
|
|
|
line: number;
|
|
|
|
|
column: number;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export async function generateSourceMapUrl(functionText: string, generatedText: string): Promise<string | undefined> {
|
|
|
|
|
const filePath = getCallerFilePath();
|
|
|
|
|
if (!filePath)
|
|
|
|
|
return;
|
|
|
|
|
try {
|
|
|
|
|
const generatedIndex = generatedText.indexOf(functionText);
|
|
|
|
|
if (generatedIndex === -1)
|
|
|
|
|
return;
|
|
|
|
|
const compiledPosition = findPosition(generatedText, generatedIndex);
|
|
|
|
|
const source = await util.promisify(fs.readFile)(filePath, 'utf8');
|
|
|
|
|
const sourceIndex = source.indexOf(functionText);
|
|
|
|
|
if (sourceIndex === -1)
|
|
|
|
|
return;
|
|
|
|
|
const sourcePosition = findPosition(source, sourceIndex);
|
|
|
|
|
const delta = findPosition(functionText, functionText.length);
|
|
|
|
|
const sourceMap = generateSourceMap(filePath, sourcePosition, compiledPosition, delta);
|
|
|
|
|
return `\n//# sourceMappingURL=data:application/json;base64,${Buffer.from(sourceMap).toString('base64')}\n`;
|
|
|
|
|
} catch (e) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const VLQ_BASE_SHIFT = 5;
|
|
|
|
|
const VLQ_BASE = 1 << VLQ_BASE_SHIFT;
|
|
|
|
|
const VLQ_BASE_MASK = VLQ_BASE - 1;
|
|
|
|
|
const VLQ_CONTINUATION_BIT = VLQ_BASE;
|
|
|
|
|
const BASE64_DIGITS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
|
|
|
|
|
|
|
|
|
function base64VLQ(value: number): string {
|
|
|
|
|
if (value < 0)
|
|
|
|
|
value = ((-value) << 1) | 1;
|
|
|
|
|
else
|
|
|
|
|
value <<= 1;
|
|
|
|
|
let result = '';
|
|
|
|
|
do {
|
|
|
|
|
let digit = value & VLQ_BASE_MASK;
|
|
|
|
|
value >>>= VLQ_BASE_SHIFT;
|
|
|
|
|
if (value > 0)
|
|
|
|
|
digit |= VLQ_CONTINUATION_BIT;
|
|
|
|
|
result += BASE64_DIGITS[digit];
|
|
|
|
|
} while (value > 0);
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function generateSourceMap(filePath: string, sourcePosition: Position, compiledPosition: Position, delta: Position): any {
|
|
|
|
|
const mappings = [];
|
|
|
|
|
let lastCompiled = { line: 0, column: 0 };
|
|
|
|
|
let lastSource = { line: 0, column: 0 };
|
|
|
|
|
for (let line = 0; line < delta.line; line++) {
|
|
|
|
|
// We need at least a mapping per line. This will yield an execution line at the start of each line.
|
|
|
|
|
// Note: for more granular mapping, we can do word-by-word.
|
|
|
|
|
const source = advancePosition(sourcePosition, { line, column: 0 });
|
|
|
|
|
const compiled = advancePosition(compiledPosition, { line, column: 0 });
|
|
|
|
|
while (lastCompiled.line < compiled.line) {
|
|
|
|
|
mappings.push(';');
|
|
|
|
|
lastCompiled.line++;
|
|
|
|
|
lastCompiled.column = 0;
|
|
|
|
|
}
|
|
|
|
|
mappings.push(base64VLQ(compiled.column - lastCompiled.column));
|
|
|
|
|
mappings.push(base64VLQ(0)); // Source index.
|
|
|
|
|
mappings.push(base64VLQ(source.line - lastSource.line));
|
|
|
|
|
mappings.push(base64VLQ(source.column - lastSource.column));
|
|
|
|
|
lastCompiled = compiled;
|
|
|
|
|
lastSource = source;
|
|
|
|
|
}
|
|
|
|
|
return JSON.stringify({
|
|
|
|
|
version: 3,
|
|
|
|
|
sources: ['file://' + filePath],
|
|
|
|
|
names: [],
|
|
|
|
|
mappings: mappings.join(''),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function findPosition(source: string, offset: number): Position {
|
|
|
|
|
const result: Position = { line: 0, column: 0 };
|
|
|
|
|
let index = 0;
|
|
|
|
|
while (true) {
|
|
|
|
|
const newline = source.indexOf('\n', index);
|
|
|
|
|
if (newline === -1 || newline >= offset)
|
|
|
|
|
break;
|
|
|
|
|
result.line++;
|
|
|
|
|
index = newline + 1;
|
|
|
|
|
}
|
|
|
|
|
result.column = offset - index;
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function advancePosition(position: Position, delta: Position) {
|
|
|
|
|
return {
|
|
|
|
|
line: position.line + delta.line,
|
|
|
|
|
column: delta.column + (delta.line ? 0 : position.column),
|
|
|
|
|
};
|
|
|
|
|
}
|