Lint and format src/compiler/utils/mapped_code.js

pull/8569/head
Simon Holthausen 3 years ago
parent 09ca9333a8
commit 6b92b39a16

@ -2,10 +2,10 @@ import remapping from '@ampproject/remapping';
import { push_array } from './push_array.js'; import { push_array } from './push_array.js';
/** /**
* @param {string} s * @param {string} s
*/ */
function last_line_length(s) { function last_line_length(s) {
return s.length - s.lastIndexOf('\n') - 1; return s.length - s.lastIndexOf('\n') - 1;
} }
// mutate map in-place // mutate map in-place
@ -15,24 +15,23 @@ function last_line_length(s) {
* @param {number} source_index * @param {number} source_index
*/ */
export function sourcemap_add_offset(map, offset, source_index) { export function sourcemap_add_offset(map, offset, source_index) {
if (map.mappings.length == 0) if (map.mappings.length == 0) return;
return; for (let line = 0; line < map.mappings.length; line++) {
for (let line = 0; line < map.mappings.length; line++) { const segment_list = map.mappings[line];
const segment_list = map.mappings[line]; for (let segment = 0; segment < segment_list.length; segment++) {
for (let segment = 0; segment < segment_list.length; segment++) { const seg = segment_list[segment];
const seg = segment_list[segment]; // shift only segments that belong to component source file
// shift only segments that belong to component source file if (seg[1] === source_index) {
if (seg[1] === source_index) { // also ensures that seg.length >= 4
// also ensures that seg.length >= 4 // shift column if it points at the first line
// shift column if it points at the first line if (seg[2] === 0) {
if (seg[2] === 0) { seg[3] += offset.column;
seg[3] += offset.column; }
} // shift line
// shift line seg[2] += offset.line;
seg[2] += offset.line; }
} }
} }
}
} }
/** /**
@ -41,206 +40,193 @@ export function sourcemap_add_offset(map, offset, source_index) {
* @returns {[T[], number[], boolean, boolean]} * @returns {[T[], number[], boolean, boolean]}
*/ */
function merge_tables(this_table, other_table) { function merge_tables(this_table, other_table) {
const new_table = this_table.slice(); const new_table = this_table.slice();
const idx_map = []; const idx_map = [];
other_table = other_table || []; other_table = other_table || [];
let val_changed = false; let val_changed = false;
for (const [other_idx, other_val] of other_table.entries()) { for (const [other_idx, other_val] of other_table.entries()) {
const this_idx = this_table.indexOf(other_val); const this_idx = this_table.indexOf(other_val);
if (this_idx >= 0) { if (this_idx >= 0) {
idx_map[other_idx] = this_idx; idx_map[other_idx] = this_idx;
} } else {
else { const new_idx = new_table.length;
const new_idx = new_table.length; new_table[new_idx] = other_val;
new_table[new_idx] = other_val; idx_map[other_idx] = new_idx;
idx_map[other_idx] = new_idx; val_changed = true;
val_changed = true; }
} }
} let idx_changed = val_changed;
let idx_changed = val_changed; if (val_changed) {
if (val_changed) { if (idx_map.find((val, idx) => val != idx) === undefined) {
if (idx_map.find((val, idx) => val != idx) === undefined) { // idx_map is identity map [0, 1, 2, 3, 4, ....]
// idx_map is identity map [0, 1, 2, 3, 4, ....] idx_changed = false;
idx_changed = false; }
} }
} return [new_table, idx_map, val_changed, idx_changed];
return [new_table, idx_map, val_changed, idx_changed];
} }
const regex_line_token = /([^\d\w\s]|\s+)/g; const regex_line_token = /([^\d\w\s]|\s+)/g;
/** */ /** */
export class MappedCode { export class MappedCode {
/**
* @type {string}
*/
string = undefined;
/** /**
* @type {string} * @type {DecodedSourceMap}
*/ */
string = undefined; map = undefined;
constructor(string = '', map = null) {
/** this.string = string;
* @type {DecodedSourceMap} if (map) {
*/ this.map = map;
map = undefined; } else {
constructor(string = '', map = null) { this.map = {
this.string = string; version: 3,
if (map) { mappings: [],
this.map = map; sources: [],
} names: []
else { };
this.map = { }
version: 3, }
mappings: [], /**
sources: [], * concat in-place (mutable), return this (chainable)
names: [] * will also mutate the `other` object
}; * @param {MappedCode} other
} * @returns {import("C:/repos/svelte/svelte/mapped_code.ts-to-jsdoc").MappedCode}
} */
/** concat(other) {
* concat in-place (mutable), return this (chainable) // noop: if one is empty, return the other
* will also mutate the `other` object if (other.string == '') return this;
* @param {MappedCode} other if (this.string == '') {
* @returns {import("C:/repos/svelte/svelte/mapped_code.ts-to-jsdoc").MappedCode} this.string = other.string;
*/ this.map = other.map;
concat(other) { return this;
// noop: if one is empty, return the other }
if (other.string == '') // compute last line length before mutating
return this; const column_offset = last_line_length(this.string);
if (this.string == '') { this.string += other.string;
this.string = other.string; const m1 = this.map;
this.map = other.map; const m2 = other.map;
return this; if (m2.mappings.length == 0) return this;
} // combine sources and names
// compute last line length before mutating const [sources, new_source_idx, sources_changed, sources_idx_changed] = merge_tables(
const column_offset = last_line_length(this.string); m1.sources,
this.string += other.string; m2.sources
const m1 = this.map; );
const m2 = other.map; const [names, new_name_idx, names_changed, names_idx_changed] = merge_tables(
if (m2.mappings.length == 0) m1.names,
return this; m2.names
// combine sources and names );
const [sources, new_source_idx, sources_changed, sources_idx_changed] = merge_tables(m1.sources, m2.sources); if (sources_changed) m1.sources = sources;
const [names, new_name_idx, names_changed, names_idx_changed] = merge_tables(m1.names, m2.names); if (names_changed) m1.names = names;
if (sources_changed) // unswitched loops are faster
m1.sources = sources; if (sources_idx_changed && names_idx_changed) {
if (names_changed) for (let line = 0; line < m2.mappings.length; line++) {
m1.names = names; const segment_list = m2.mappings[line];
// unswitched loops are faster for (let segment = 0; segment < segment_list.length; segment++) {
if (sources_idx_changed && names_idx_changed) { const seg = segment_list[segment];
for (let line = 0; line < m2.mappings.length; line++) { if (seg[1] >= 0) seg[1] = new_source_idx[seg[1]];
const segment_list = m2.mappings[line]; if (seg[4] >= 0) seg[4] = new_name_idx[seg[4]];
for (let segment = 0; segment < segment_list.length; segment++) { }
const seg = segment_list[segment]; }
if (seg[1] >= 0) } else if (sources_idx_changed) {
seg[1] = new_source_idx[seg[1]]; for (let line = 0; line < m2.mappings.length; line++) {
if (seg[4] >= 0) const segment_list = m2.mappings[line];
seg[4] = new_name_idx[seg[4]]; for (let segment = 0; segment < segment_list.length; segment++) {
} const seg = segment_list[segment];
} if (seg[1] >= 0) seg[1] = new_source_idx[seg[1]];
} }
else if (sources_idx_changed) { }
for (let line = 0; line < m2.mappings.length; line++) { } else if (names_idx_changed) {
const segment_list = m2.mappings[line]; for (let line = 0; line < m2.mappings.length; line++) {
for (let segment = 0; segment < segment_list.length; segment++) { const segment_list = m2.mappings[line];
const seg = segment_list[segment]; for (let segment = 0; segment < segment_list.length; segment++) {
if (seg[1] >= 0) const seg = segment_list[segment];
seg[1] = new_source_idx[seg[1]]; if (seg[4] >= 0) seg[4] = new_name_idx[seg[4]];
} }
} }
} }
else if (names_idx_changed) { // combine the mappings
for (let line = 0; line < m2.mappings.length; line++) { // combine
const segment_list = m2.mappings[line]; // 1. last line of first map
for (let segment = 0; segment < segment_list.length; segment++) { // 2. first line of second map
const seg = segment_list[segment]; // columns of 2 must be shifted
if (seg[4] >= 0) if (m2.mappings.length > 0 && column_offset > 0) {
seg[4] = new_name_idx[seg[4]]; const first_line = m2.mappings[0];
} for (let i = 0; i < first_line.length; i++) {
} first_line[i][0] += column_offset;
} }
// combine the mappings }
// combine // combine last line + first line
// 1. last line of first map push_array(m1.mappings[m1.mappings.length - 1], m2.mappings.shift());
// 2. first line of second map // append other lines
// columns of 2 must be shifted push_array(m1.mappings, m2.mappings);
if (m2.mappings.length > 0 && column_offset > 0) { return this;
const first_line = m2.mappings[0]; }
for (let i = 0; i < first_line.length; i++) {
first_line[i][0] += column_offset;
}
}
// combine last line + first line
push_array(m1.mappings[m1.mappings.length - 1], m2.mappings.shift());
// append other lines
push_array(m1.mappings, m2.mappings);
return this;
}
/** /**
* @static * @static
* @param {string} string * @param {string} string
* @param {DecodedSourceMap} [map] * @param {DecodedSourceMap} [map]
* @returns {import("C:/repos/svelte/svelte/mapped_code.ts-to-jsdoc").MappedCode} * @returns {import("C:/repos/svelte/svelte/mapped_code.ts-to-jsdoc").MappedCode}
*/ */
static from_processed(string, map) { static from_processed(string, map) {
const line_count = string.split('\n').length; const line_count = string.split('\n').length;
if (map) { if (map) {
// ensure that count of source map mappings lines // ensure that count of source map mappings lines
// is equal to count of generated code lines // is equal to count of generated code lines
// (some tools may produce less) // (some tools may produce less)
const missing_lines = line_count - map.mappings.length; const missing_lines = line_count - map.mappings.length;
for (let i = 0; i < missing_lines; i++) { for (let i = 0; i < missing_lines; i++) {
map.mappings.push([]); map.mappings.push([]);
} }
return new MappedCode(string, map); return new MappedCode(string, map);
} }
if (string == '') if (string == '') return new MappedCode();
return new MappedCode(); map = { version: 3, names: [], sources: [], mappings: [] };
map = { version: 3, names: [], sources: [], mappings: [] }; // add empty SourceMapSegment[] for every line
// add empty SourceMapSegment[] for every line for (let i = 0; i < line_count; i++) map.mappings.push([]);
for (let i = 0; i < line_count; i++) return new MappedCode(string, map);
map.mappings.push([]); }
return new MappedCode(string, map);
}
/** /**
* @static * @static
* @param {Source} * @param {Source}
* @returns {import("C:/repos/svelte/svelte/mapped_code.ts-to-jsdoc").MappedCode} * @returns {import("C:/repos/svelte/svelte/mapped_code.ts-to-jsdoc").MappedCode}
*/ */
static from_source({ source, file_basename, get_location }) { static from_source({ source, file_basename, get_location }) {
/**
/** * @type {SourceLocation}
* @type {SourceLocation} */
*/ let offset = get_location(0);
let offset = get_location(0); if (!offset) offset = { line: 0, column: 0 };
if (!offset)
offset = { line: 0, column: 0 };
/** /**
* @type {DecodedSourceMap} * @type {DecodedSourceMap}
*/ */
const map = { version: 3, names: [], sources: [file_basename], mappings: [] }; const map = { version: 3, names: [], sources: [file_basename], mappings: [] };
if (source == '') if (source == '') return new MappedCode(source, map);
return new MappedCode(source, map); // we create a high resolution identity map here,
// we create a high resolution identity map here, // we know that it will eventually be merged with svelte's map,
// we know that it will eventually be merged with svelte's map, // at which stage the resolution will decrease.
// at which stage the resolution will decrease. const line_list = source.split('\n');
const line_list = source.split('\n'); for (let line = 0; line < line_list.length; line++) {
for (let line = 0; line < line_list.length; line++) { map.mappings.push([]);
map.mappings.push([]); const token_list = line_list[line].split(regex_line_token);
const token_list = line_list[line].split(regex_line_token); for (let token = 0, column = 0; token < token_list.length; token++) {
for (let token = 0, column = 0; token < token_list.length; token++) { if (token_list[token] == '') continue;
if (token_list[token] == '') map.mappings[line].push([column, 0, offset.line + line, column]);
continue; column += token_list[token].length;
map.mappings[line].push([column, 0, offset.line + line, column]); }
column += token_list[token].length; }
} // shift columns in first line
} const segment_list = map.mappings[0];
// shift columns in first line for (let segment = 0; segment < segment_list.length; segment++) {
const segment_list = map.mappings[0]; segment_list[segment][3] += offset.column;
for (let segment = 0; segment < segment_list.length; segment++) { }
segment_list[segment][3] += offset.column; return new MappedCode(source, map);
} }
return new MappedCode(source, map);
}
} }
/** /**
@ -248,35 +234,36 @@ export class MappedCode {
* @param {Array<DecodedSourceMap | RawSourceMap>} sourcemap_list * @param {Array<DecodedSourceMap | RawSourceMap>} sourcemap_list
*/ */
export function combine_sourcemaps(filename, sourcemap_list) { export function combine_sourcemaps(filename, sourcemap_list) {
if (sourcemap_list.length == 0) if (sourcemap_list.length == 0) return null;
return null; let map_idx = 1;
let map_idx = 1; const map =
const map = sourcemap_list.slice(0, -1).find((m) => m.sources.length !== 1) === undefined sourcemap_list.slice(0, -1).find((m) => m.sources.length !== 1) === undefined
? remapping( ? remapping(
// use array interface // use array interface
// only the oldest sourcemap can have multiple sources // only the oldest sourcemap can have multiple sources
sourcemap_list, () => null, true // skip optional field `sourcesContent` sourcemap_list,
) () => null,
: remapping( true // skip optional field `sourcesContent`
// use loader interface )
sourcemap_list[0], // last map : remapping(
function loader(sourcefile) { // use loader interface
if (sourcefile === filename && sourcemap_list[map_idx]) { sourcemap_list[0], // last map
return sourcemap_list[map_idx++]; // idx 1, 2, ... (sourcefile) => {
// bundle file = branch node if (sourcefile === filename && sourcemap_list[map_idx]) {
} return sourcemap_list[map_idx++]; // idx 1, 2, ...
else { // bundle file = branch node
return null; // source file = leaf node } else {
} return null; // source file = leaf node
}, true); }
if (!map.file) },
delete map.file; // skip optional field `file` true
// When source maps are combined and the leading map is empty, sources is not set. );
// Add the filename to the empty array in this case. if (!map.file) delete map.file; // skip optional field `file`
// Further improvements to remapping may help address this as well https://github.com/ampproject/remapping/issues/116 // When source maps are combined and the leading map is empty, sources is not set.
if (!map.sources.length) // Add the filename to the empty array in this case.
map.sources = [filename]; // Further improvements to remapping may help address this as well https://github.com/ampproject/remapping/issues/116
return map; if (!map.sources.length) map.sources = [filename];
return map;
} }
// browser vs node.js // browser vs node.js
const b64enc = typeof btoa == 'function' ? btoa : (b) => Buffer.from(b).toString('base64'); const b64enc = typeof btoa == 'function' ? btoa : (b) => Buffer.from(b).toString('base64');
@ -289,32 +276,29 @@ const b64dec = typeof atob == 'function' ? atob : (a) => Buffer.from(a, 'base64'
* @returns {SourceMap} * @returns {SourceMap}
*/ */
export function apply_preprocessor_sourcemap(filename, svelte_map, preprocessor_map_input) { export function apply_preprocessor_sourcemap(filename, svelte_map, preprocessor_map_input) {
if (!svelte_map || !preprocessor_map_input) if (!svelte_map || !preprocessor_map_input) return svelte_map;
return svelte_map; const preprocessor_map =
const preprocessor_map = typeof preprocessor_map_input === 'string' typeof preprocessor_map_input === 'string'
? JSON.parse(preprocessor_map_input) ? JSON.parse(preprocessor_map_input)
: preprocessor_map_input; : preprocessor_map_input;
const result_map = combine_sourcemaps(filename, [ const result_map = combine_sourcemaps(filename, [svelte_map, preprocessor_map]);
svelte_map, // Svelte expects a SourceMap which includes toUrl and toString. Instead of wrapping our output in a class,
preprocessor_map // we just tack on the extra properties.
]); Object.defineProperties(result_map, {
// Svelte expects a SourceMap which includes toUrl and toString. Instead of wrapping our output in a class, toString: {
// we just tack on the extra properties. enumerable: false,
Object.defineProperties(result_map, { value: function toString() {
toString: { return JSON.stringify(this);
enumerable: false, }
value: function toString() { },
return JSON.stringify(this); toUrl: {
} enumerable: false,
}, value: function toUrl() {
toUrl: { return 'data:application/json;charset=utf-8;base64,' + b64enc(this.toString());
enumerable: false, }
value: function toUrl() { }
return 'data:application/json;charset=utf-8;base64,' + b64enc(this.toString()); });
} return result_map;
}
});
return result_map;
} }
const regex_data_uri = /data:(?:application|text)\/json;(?:charset[:=]\S+?;)?base64,(\S*)/; const regex_data_uri = /data:(?:application|text)\/json;(?:charset[:=]\S+?;)?base64,(\S*)/;
// parse attached sourcemap in processed.code // parse attached sourcemap in processed.code
@ -325,49 +309,55 @@ const regex_data_uri = /data:(?:application|text)\/json;(?:charset[:=]\S+?;)?bas
* @returns {void} * @returns {void}
*/ */
export function parse_attached_sourcemap(processed, tag_name) { export function parse_attached_sourcemap(processed, tag_name) {
const r_in = '[#@]\\s*sourceMappingURL\\s*=\\s*(\\S*)'; const r_in = '[#@]\\s*sourceMappingURL\\s*=\\s*(\\S*)';
const regex = tag_name == 'script' const regex =
? new RegExp('(?://' + r_in + ')|(?:/\\*' + r_in + '\\s*\\*/)$') tag_name == 'script'
: new RegExp('/\\*' + r_in + '\\s*\\*/$'); ? new RegExp('(?://' + r_in + ')|(?:/\\*' + r_in + '\\s*\\*/)$')
: new RegExp('/\\*' + r_in + '\\s*\\*/$');
/** /**
* @param {any} message * @param {any} message
*/ */
function log_warning(message) { function log_warning(message) {
// code_start: help to find preprocessor // code_start: help to find preprocessor
const code_start = processed.code.length < 100 ? processed.code : processed.code.slice(0, 100) + ' [...]'; const code_start =
console.warn(`warning: ${message}. processed.code = ${JSON.stringify(code_start)}`); processed.code.length < 100 ? processed.code : processed.code.slice(0, 100) + ' [...]';
} console.warn(`warning: ${message}. processed.code = ${JSON.stringify(code_start)}`);
processed.code = processed.code.replace(regex, (_, match1, match2) => { }
const map_url = tag_name == 'script' ? match1 || match2 : match1; processed.code = processed.code.replace(regex, (_, match1, match2) => {
const map_data = (map_url.match(regex_data_uri) || [])[1]; const map_url = tag_name == 'script' ? match1 || match2 : match1;
if (map_data) { const map_data = (map_url.match(regex_data_uri) || [])[1];
// sourceMappingURL is data URL if (map_data) {
if (processed.map) { // sourceMappingURL is data URL
log_warning('Not implemented. ' + if (processed.map) {
'Found sourcemap in both processed.code and processed.map. ' + log_warning(
'Please update your preprocessor to return only one sourcemap.'); 'Not implemented. ' +
// ignore attached sourcemap 'Found sourcemap in both processed.code and processed.map. ' +
return ''; 'Please update your preprocessor to return only one sourcemap.'
} );
processed.map = b64dec(map_data); // use attached sourcemap // ignore attached sourcemap
return ''; // remove from processed.code return '';
} }
// sourceMappingURL is path or URL processed.map = b64dec(map_data); // use attached sourcemap
if (!processed.map) { return ''; // remove from processed.code
log_warning(`Found sourcemap path ${JSON.stringify(map_url)} in processed.code, but no sourcemap data. ` + }
'Please update your preprocessor to return sourcemap data directly.'); // sourceMappingURL is path or URL
} if (!processed.map) {
// ignore sourcemap path log_warning(
return ''; // remove from processed.code `Found sourcemap path ${JSON.stringify(
}); map_url
)} in processed.code, but no sourcemap data. ` +
'Please update your preprocessor to return sourcemap data directly.'
);
}
// ignore sourcemap path
return ''; // remove from processed.code
});
} }
/** /**
* @typedef {{ * @typedef {{
* line: number; * line: number;
* column: number; * column: number;
* }} SourceLocation * }} SourceLocation
*/ */

Loading…
Cancel
Save