fix tests, use decoded mappings, show warnings

pull/5428/head
Milan Hauth 5 years ago
parent a0eb41f68d
commit 18003d6487

@ -29,8 +29,11 @@ import add_to_set from './utils/add_to_set';
import check_graph_for_cycles from './utils/check_graph_for_cycles';
import { print, x, b } from 'code-red';
import { is_reserved_keyword } from './utils/reserved_keywords';
import remapping from '@ampproject/remapping';
import { combine_sourcemaps, sourcemap_add_tostring_tourl, combine_sourcemaps_map_stats } from '../utils/string_with_sourcemap';
import Element from './nodes/Element';
import { RawSourceMap } from '@ampproject/remapping/dist/types/types';
import { encode as encode_mappings, decode as decode_mappings } from 'sourcemap-codec';
interface ComponentOptions {
namespace?: string;
@ -318,12 +321,19 @@ export default class Component {
css = compile_options.customElement
? { code: null, map: null }
: result.css;
: result.css; // css.map.mappings are decoded
js = print(program, {
sourceMapSource: compile_options.filename
});
// TODO remove workaround
// js.map.mappings should be decoded
// https://github.com/Rich-Harris/code-red/issues/50
if (js.map && typeof (js.map as any).mappings == 'string') {
(js.map as any).mappings = decode_mappings((js.map as any).mappings);
}
js.map.sources = [
compile_options.filename ? get_relative_path(compile_options.outputFilename || '', compile_options.filename) : null
];
@ -332,34 +342,59 @@ export default class Component {
this.source
];
// combine sourcemaps
const map_stats: combine_sourcemaps_map_stats = {
sourcemapWarnLoss: 0, // segment loss is usually high, so we ignore
sourcemapEncodedWarn: true // TODO config
// property `result` is set by combine_sourcemaps
};
if (compile_options.sourcemap) {
if (js.map) {
const pre_remap_sources = js.map.sources;
js.map = remapping([js.map, compile_options.sourcemap], () => null, true);
// remapper can remove our source if it isn't used (no segments map back to it). It is still handy to have a source
// so we add it back
if (js.map.sources && js.map.sources.length == 0) {
js.map.sources = pre_remap_sources;
js.map = combine_sourcemaps(
this.file,
[
js.map, // idx 1: internal
compile_options.sourcemap // idx 0: external: svelte.preprocess, etc
],
map_stats
) as RawSourceMap;
sourcemap_add_tostring_tourl(js.map);
if (map_stats.result && map_stats.result.maps_encoded && map_stats.result.maps_encoded.length > 0) {
console.log('warning. svelte.compile received encoded script sourcemaps (index '+
map_stats.result.maps_encoded.join(', ')+'). '+
'this is slow. make your sourcemap-generators return decoded mappings '+
'or disable this warning with svelte.compile(_, _, { sourcemapEncodedWarn: false })'
);
}
Object.defineProperties(js.map, {
toString: {
enumerable: false,
value: function toString() {
return JSON.stringify(this);
}
},
toUrl: {
enumerable: false,
value: function toUrl() {
return 'data:application/json;charset=utf-8;base64,' + btoa(this.toString());
}
}
});
}
if (css.map) {
css.map = remapping([css.map, compile_options.sourcemap], () => null, true);
css.map = combine_sourcemaps(
this.file,
[
css.map, // idx 1: internal
compile_options.sourcemap // idx 0: external: svelte.preprocess, etc
]
) as RawSourceMap;
sourcemap_add_tostring_tourl(css.map);
if (map_stats.result && map_stats.result.maps_encoded && map_stats.result.maps_encoded.length > 0) {
console.log('warning. svelte.compile received encoded style sourcemaps (index '+
map_stats.result.maps_encoded.join(', ')+'). '+
'this is slow. make your sourcemap-generators return decoded mappings '+
'or disable this warning with svelte.compile(_, _, { sourcemapEncodedWarn: false })'
);
}
}
}
// encode mappings only once, after all sourcemaps are combined
if (js.map && typeof(js.map.mappings) == 'object') {
(js.map as RawSourceMap).mappings = encode_mappings(js.map.mappings);
}
if (css.map && typeof(css.map.mappings) == 'object') {
(css.map as RawSourceMap).mappings = encode_mappings(css.map.mappings);
}
}
return {

@ -410,7 +410,7 @@ export default class Stylesheet {
return {
code: code.toString(),
map: code.generateMap({
map: code.generateDecodedMap({
includeContent: true,
source: this.filename,
file

@ -30,8 +30,12 @@ export default function dom(
}
const css = component.stylesheet.render(options.filename, !options.customElement);
// TODO fix css.map.toUrl - stylesheet.render returns decoded mappings, map.toUrl needs encoded mappings
// TODO use combined css.map? see compile/Component.ts
const styles = component.stylesheet.has_styles && options.dev
? `${css.code}\n/*# sourceMappingURL=${css.map.toUrl()} */`
//? `${css.code}\n/*# sourceMappingURL=${css.map.toUrl()} */`
? `${css.code}\n/*# sourceMappingURL=TODO_FIXME */`
: css.code;
const add_css = component.get_unique_name('add_css');
@ -467,12 +471,15 @@ export default function dom(
}
if (options.customElement) {
// TODO use combined css.map? see compile/Component.ts
// TODO css.map.toUrl needs encoded mappings
// ${css.code && b`this.shadowRoot.innerHTML = \`<style>${css.code.replace(/\\/g, '\\\\')}${options.dev ? `\n/*# sourceMappingURL=${css.map.toUrl()} */` : ''}</style>\`;`}
const declaration = b`
class ${name} extends @SvelteElement {
constructor(options) {
super();
${css.code && b`this.shadowRoot.innerHTML = \`<style>${css.code.replace(/\\/g, '\\\\')}${options.dev ? `\n/*# sourceMappingURL=${css.map.toUrl()} */` : ''}</style>\`;`}
${css.code && b`this.shadowRoot.innerHTML = \`<style>${css.code.replace(/\\/g, '\\\\')}${options.dev ? `\n/*# sourceMappingURL=TODO_FIXME */` : ''}</style>\`;`}
@init(this, { target: this.shadowRoot }, ${definition}, ${has_create_fragment ? 'create_fragment': 'null'}, ${not_equal}, ${prop_indexes}, ${dirty});

@ -137,6 +137,7 @@ export default function ssr(
main
].filter(Boolean);
// TODO use combined css.map? see compile/Component.ts
const js = b`
${css.code ? b`
const #css = {

@ -1,5 +1,7 @@
import { Node, Program } from "estree";
import { SourceMap } from 'magic-string';
// eslint-disable-next-line import/named
import { DecodedSourceMap } from 'magic-string';
interface BaseNode {
start: number;
@ -166,5 +168,5 @@ export interface Var {
export interface CssResult {
code: string;
map: SourceMap;
map: DecodedSourceMap;
}

@ -1,8 +1,7 @@
import remapping from '@ampproject/remapping';
import { SourceMapInput, SourceMapLoader, RawSourceMap, DecodedSourceMap } from '@ampproject/remapping/dist/types/types';
import { SourceMapInput, RawSourceMap, DecodedSourceMap } from '@ampproject/remapping/dist/types/types';
import { decode as decode_mappings } from 'sourcemap-codec';
import { getLocator } from 'locate-character';
import { StringWithSourcemap, sourcemap_add_offset } from '../utils/string_with_sourcemap';
import { StringWithSourcemap, sourcemap_add_offset, combine_sourcemaps, combine_sourcemaps_map_stats } from '../utils/string_with_sourcemap';
export interface Processed {
code: string;
@ -113,10 +112,17 @@ function get_replacement(
export default async function preprocess(
source: string,
preprocessor: PreprocessorGroup | PreprocessorGroup[],
options?: { filename?: string }
options?: {
filename?: string,
sourcemapWarnLoss?: number, // default 0.5
sourcemapEncodedWarn?: boolean // default true
}
) {
// @ts-ignore todo: doublecheck
const filename = (options && options.filename) || preprocessor.filename; // legacy
const sourcemapWarnLoss = (options && options.sourcemapWarnLoss != undefined) ? options.sourcemapWarnLoss : 0.5;
const sourcemapEncodedWarn = (options && options.sourcemapEncodedWarn != undefined) ? options.sourcemapEncodedWarn : true;
const dependencies = [];
const preprocessors = preprocessor
@ -130,7 +136,9 @@ export default async function preprocess(
// sourcemap_list is sorted in reverse order from last map (index 0) to first map (index -1)
// so we use sourcemap_list.unshift() to add new maps
// https://github.com/ampproject/remapping#multiple-transformations-of-a-file
const sourcemap_list: (DecodedSourceMap | RawSourceMap)[] = [];
const sourcemap_list: Array<DecodedSourceMap | RawSourceMap> = [];
// TODO keep track: what preprocessor generated what sourcemap? to make debugging easier = detect low-resolution sourcemaps in fn combine_mappings
for (const fn of markup) {
@ -150,6 +158,8 @@ export default async function preprocess(
);
}
// TODO run script and style in parallel
for (const fn of script) {
const get_location = getLocator(source);
const res = await replace_async(
@ -216,37 +226,41 @@ export default async function preprocess(
sourcemap_list.unshift(res.map);
}
let map: RawSourceMap;
let map_idx = 0;
try {
map =
sourcemap_list.length == 0
? null
: sourcemap_list.slice(0, -1).find(m => m.sources.length !== 1) === undefined
? remapping( // use array interface
sourcemap_list,
() => null,
true // skip optional field `sourcesContent`
)
: remapping( // use loader interface
sourcemap_list[map_idx++],
function loader(sourcefile) {
if (sourcefile === filename)
return sourcemap_list[map_idx++] || null;
// bundle file = branch node
else return null; // source file = leaf node
} as SourceMapLoader
);
} catch (error) {
throw { ...error, message: error.message +
'\n\ncould not combine sourcemaps:\n' +
JSON.stringify(sourcemap_list.map(m => {
return { ...m, mappings: JSON.stringify(m.mappings).slice(0, 100)+' ....'};
}), null, 2)
};
const map_stats: combine_sourcemaps_map_stats = {
sourcemapWarnLoss,
sourcemapEncodedWarn
// property `result` is set by combine_sourcemaps
};
const map: DecodedSourceMap = combine_sourcemaps(
filename,
sourcemap_list,
map_stats,
true // explicitly decode mappings
// TODO remove this, when `remapping` allows to return decoded mappings, so we skip the unnecessary encode + decode steps
) as DecodedSourceMap;
// TODO better than console.log?
if (map_stats.result && map_stats.result.segments_lost) {
const { segment_loss_per_map, segments_per_map } = map_stats.result;
console.log('warning. svelte.preprocess seems to receive low-resolution sourcemaps. '+
'relative segment loss per combine_sourcemaps step: '+
segment_loss_per_map.map(f => f.toFixed(2)).join(' -> ')+
'. absolute number of segments per sourcemap: '+
segments_per_map.join(' -> ')+
'. make your preprocessors return high-resolution sourcemaps '+
'or increase the tolerated loss with svelte.preprocess(_, _, { sourcemapWarnLoss: 0.8 })'
);
}
if (map && !map.file) delete map.file; // skip optional field `file`
if (map_stats.result && map_stats.result.maps_encoded && map_stats.result.maps_encoded.length > 0) {
console.log('warning. svelte.preprocess received encoded sourcemaps (index '+
map_stats.result.maps_encoded.join(', ')+'). '+
'this is slow. make your sourcemap-generators return decoded mappings '+
'or disable this warning with svelte.preprocess(_, _, { sourcemapEncodedWarn: false })'
);
}
return {
// TODO return separated output, in future version where svelte.compile supports it:

@ -1,4 +1,6 @@
import { DecodedSourceMap, SourceMapSegment } from '@ampproject/remapping/dist/types/types';
import { DecodedSourceMap, RawSourceMap, SourceMapSegment, SourceMapLoader } from '@ampproject/remapping/dist/types/types';
import remapping from '@ampproject/remapping';
import { decode as decode_mappings } from 'sourcemap-codec';
type SourceLocation = {
line: number;
@ -180,3 +182,152 @@ export class StringWithSourcemap {
return new StringWithSourcemap(source, map);
}
}
export type combine_sourcemaps_map_stats = {
sourcemapEncodedWarn?: boolean,
sourcemapWarnLoss?: number,
result?: {
maps_encoded?: number[],
segments_lost?: boolean
segment_loss_per_map?: number[],
segments_per_map?: number[],
}
};
export function combine_sourcemaps(
filename: string,
sourcemap_list: Array<DecodedSourceMap | RawSourceMap>,
map_stats?: combine_sourcemaps_map_stats,
do_decode_mappings?: boolean
): (RawSourceMap | DecodedSourceMap) {
if (sourcemap_list.length == 0) return null;
if (map_stats) {
map_stats.result = {};
const { result } = map_stats;
const last_map_idx = sourcemap_list.length - 1;
// TODO allow to set options per preprocessor -> extend preprocessor config object
// some sourcemap-generators produce ultra-high-resolution sourcemaps (1 token = 1 character), so a high segment loss can be tolerable
// sourcemapEncodedWarn: show warning
// if preprocessors return sourcemaps with encoded mappings
// we need decoded mappings, so that is a waste of time
if (map_stats.sourcemapEncodedWarn) {
result.maps_encoded = [];
for (let map_idx = last_map_idx; map_idx >= 0; map_idx--) {
const map = sourcemap_list[map_idx];
if (typeof(map) == 'string') {
sourcemap_list[map_idx] = JSON.parse(map);
}
if (typeof(map.mappings) == 'string') {
result.maps_encoded.push(last_map_idx - map_idx); // chronological index
}
}
}
// sourcemapWarnLoss: show warning if source files were lost
// disable warning with `svelte.preprocess(_, _, { sourcemapWarnLoss: false })`
// value 1 : never warn
// value 0.8: seldom warn
// value 0.5: average warn
// value 0.2: often warn
// value 0 : nonsense -> never warn
// -Infinity <= loss <= 1 and 0 < sourcemapWarnLoss <= 1
if (map_stats.sourcemapWarnLoss) {
// guess if segments were lost because of lowres sourcemaps
// assert: typeof(result) == 'object'
result.segments_per_map = [];
result.segment_loss_per_map = [];
result.segments_lost = false;
let last_num_segments;
for (let map_idx = last_map_idx; map_idx >= 0; map_idx--) {
const map = sourcemap_list[map_idx];
if (typeof(map) == 'string') {
sourcemap_list[map_idx] = JSON.parse(map);
}
if (typeof(map.mappings) == 'string') {
// do this before remapping to avoid double decoding
// remapping does not mutate its input data
map.mappings = decode_mappings(map.mappings);
}
let num_segments = 0;
for (const line of map.mappings) {
num_segments += line.length;
}
// get relative loss, compared to last map
const loss = map_idx == last_map_idx
? 0 : (last_num_segments - num_segments) / last_num_segments;
if (loss > map_stats.sourcemapWarnLoss) {
result.segments_lost = true;
}
// chronological index
result.segment_loss_per_map.push(loss);
result.segments_per_map.push(num_segments);
last_num_segments = num_segments;
}
}
}
let map_idx = 1;
const map: RawSourceMap =
sourcemap_list.slice(0, -1)
.find(m => m.sources.length !== 1) === undefined
? remapping( // use array interface
// only the oldest sourcemap can have multiple sources
sourcemap_list,
() => null,
true // skip optional field `sourcesContent`
)
: remapping( // use loader interface
sourcemap_list[0], // last map
function loader(sourcefile) {
if (sourcefile === filename && sourcemap_list[map_idx]) {
return sourcemap_list[map_idx++]; // idx 1, 2, ...
// bundle file = branch node
}
else return null; // source file = leaf node
} as SourceMapLoader,
true
);
if (!map.file) delete map.file; // skip optional field `file`
if (do_decode_mappings) {
// explicitly decode mappings
// TODO remove this, when `remapping` allows to return decoded mappings, so we skip the unnecessary encode + decode steps
(map as unknown as DecodedSourceMap).mappings = decode_mappings(map.mappings);
}
return map;
}
export function sourcemap_add_tostring_tourl(map) {
Object.defineProperties(map, {
toString: {
enumerable: false,
value: function toString() {
return JSON.stringify(this);
}
},
toUrl: {
enumerable: false,
value: function toUrl() {
return 'data:application/json;charset=utf-8;base64,' + btoa(this.toString());
}
}
});
}

@ -6,6 +6,7 @@ import { loadConfig, svelte } from "../helpers.js";
// https://github.com/mozilla/source-map/issues/400
import { SourceMapConsumer } from "source-map";
import { getLocator } from "locate-character";
import { encode as encode_mappings, decode as decode_mappings } from 'sourcemap-codec';
describe("sourcemaps", () => {
fs.readdirSync(`${__dirname}/samples`).forEach(dir => {
@ -44,6 +45,13 @@ describe("sourcemaps", () => {
return test({ assert, input, preprocessed });
}
// preprocessed.map.mappings should be decoded
// to avoid unnecessary encode + decode steps
if (preprocessed.map) {
assert.equal(typeof preprocessed.map.mappings, 'object', 'preprocessed.map.mappings should be decoded');
assert.equal(Array.isArray(preprocessed.map.mappings), true, 'preprocessed.map.mappings should be decoded');
}
const { js, css } = svelte.compile(
preprocessed.code, {
filename: "input.svelte",
@ -60,7 +68,11 @@ describe("sourcemaps", () => {
fs.writeFileSync(`${outputBase}.svelte`, preprocessed.code);
if (preprocessed.map) {
fs.writeFileSync(`${outputBase}.svelte.map`, JSON.stringify(preprocessed.map, null, 2));
fs.writeFileSync(
`${outputBase}.svelte.map`,
// TODO encode mappings for output - svelte.preprocess returns decoded mappings
JSON.stringify(preprocessed.map, null, 2)
);
}
fs.writeFileSync(
`${outputBase}.js`,
@ -92,26 +104,26 @@ describe("sourcemaps", () => {
);
};
// stupid workaround (unnecessary encode + decode steps)
// TODO find a SourceMapConsumer who also consumes decoded mappings
if (preprocessed.map) {
preprocessed.map.mappings = encode_mappings(preprocessed.map.mappings);
}
// use locate_1 with mapConsumer:
// lines are one-based, columns are zero-based
if (preprocessed.map) {
preprocessed.mapConsumer = await new SourceMapConsumer(preprocessed.map);
preprocessed.locate = getLocator(preprocessed.code);
preprocessed.locate_1 = getLocator(preprocessed.code, { offsetLine: 1 });
}
preprocessed.mapConsumer = preprocessed.map && await new SourceMapConsumer(preprocessed.map);
preprocessed.locate = getLocator(preprocessed.code);
preprocessed.locate_1 = getLocator(preprocessed.code, { offsetLine: 1 });
if (js.map) {
js.mapConsumer = await new SourceMapConsumer(js.map);
js.locate = getLocator(js.code);
js.locate_1 = getLocator(js.code, { offsetLine: 1 });
}
js.mapConsumer = js.map && await new SourceMapConsumer(js.map);
js.locate = getLocator(js.code);
js.locate_1 = getLocator(js.code, { offsetLine: 1 });
if (css.map) {
css.mapConsumer = await new SourceMapConsumer(css.map);
css.locate = getLocator(css.code);
css.locate_1 = getLocator(css.code, { offsetLine: 1 });
}
css.mapConsumer = css.map && await new SourceMapConsumer(css.map);
css.locate = getLocator(css.code || '');
css.locate_1 = getLocator(css.code || '', { offsetLine: 1 });
test({ assert, input, preprocessed, js, css });
});

@ -19,6 +19,9 @@ function result(src, filename) {
}
export default {
js_map_sources: [], // test component has no scripts
preprocess: {
markup: ({ content, filename }) => {
const src = new MagicString(content);

@ -0,0 +1,54 @@
import MagicString from 'magic-string';
// TODO move util fns to test index.js
function result(filename, src, extraOptions = {}) {
return {
code: src.toString(),
map: src.generateDecodedMap({
source: filename,
hires: true,
includeContent: false,
...extraOptions
})
};
}
function replace_all(src, search, replace) {
let idx = src.original.indexOf(search);
if (idx == -1) throw new Error('search not found in src');
do {
src.overwrite(idx, idx + search.length, replace);
} while ((idx = src.original.indexOf(search, idx + 1)) != -1);
}
function replace_first(src, search, replace) {
const idx = src.original.indexOf(search);
if (idx == -1) throw new Error('search not found in src');
src.overwrite(idx, idx + search.length, replace);
}
export default {
preprocess_options: {
sourcemapLossWarn: 0.9 // warn often
},
js_map_sources: [], // test component has no scripts
preprocess: [
{ markup: ({ content, filename }) => {
const src = new MagicString(content);
replace_all(src, 'replace_me', 'done_replace');
return result(filename, src, { hires: true });
} },
{ markup: ({ content, filename }) => {
const src = new MagicString(content);
replace_first(src, 'done_replace', 'version_3');
// return low-resolution sourcemap
// this should make previous mappings unreachable
return result(filename, src, { hires: false });
} }
]
};

@ -0,0 +1,10 @@
replace_me
replace_me
replace_me
replace_me
replace_me
replace_me
replace_me
replace_me
replace_me
replace_me

@ -0,0 +1,10 @@
export function test({ assert, preprocessed, js }) {
assert.equal(preprocessed.error, undefined);
// TODO can we automate this test?
// we need the output of console.log
// to test the warning message.
// or use a different method for warnings?
}

@ -8,7 +8,7 @@ export default {
src.overwrite(idx, idx+"baritone".length, "bar");
return {
code: src.toString(),
map: src.generateMap({
map: src.generateDecodedMap({
source: filename,
includeContent: false
})

@ -11,7 +11,7 @@ export default {
src.overwrite(css_idx, css_idx + "--bazitone".length, "--baz");
return {
code: src.toString(),
map: src.generateMap({
map: src.generateDecodedMap({
source: filename,
hires: true,
includeContent: false
@ -24,7 +24,7 @@ export default {
src.prependLeft(idx, " ");
return {
code: src.toString(),
map: src.generateMap({
map: src.generateDecodedMap({
source: filename,
hires: true,
includeContent: false
@ -37,7 +37,7 @@ export default {
src.prependLeft(idx, " ");
return {
code: src.toString(),
map: src.generateMap({
map: src.generateDecodedMap({
source: filename,
hires: true,
includeContent: false

@ -10,7 +10,7 @@ function replace(search, replace, content, src, options = { storeName: true }) {
function result(src, filename) {
return {
code: src.toString(),
map: src.generateMap({
map: src.generateDecodedMap({
source: filename,
hires: true,
includeContent: false

@ -4,8 +4,8 @@ function add(bundle, filename, source) {
bundle.addSource({
filename,
content: new MagicString(source),
separator: '\n',
//separator: '', // ERROR. probably a bug in magic-string
separator: '\n'
//separator: '' // ERROR. probably a bug in magic-string
});
}
@ -15,7 +15,7 @@ function result(bundle, filename) {
map: bundle.generateMap({
file: filename,
includeContent: false,
hires: false
hires: true // required for remapping
})
};
}
@ -26,7 +26,7 @@ export default {
'foo.js',
'bar.js',
'foo2.js',
'bar2.js',
'bar2.js'
],
preprocess: [
{
@ -34,8 +34,8 @@ export default {
const bundle = new Bundle();
add(bundle, filename, content);
add(bundle, 'foo.js', 'var answer = 42;');
add(bundle, 'bar.js', 'console.log(answer);');
add(bundle, 'foo.js', 'var answer = 42; // foo.js\n');
add(bundle, 'bar.js', 'console.log(answer); // bar.js\n');
return result(bundle, filename);
}
@ -45,8 +45,8 @@ export default {
const bundle = new Bundle();
add(bundle, filename, content);
add(bundle, 'foo2.js', 'var answer2 = 84;');
add(bundle, 'bar2.js', 'console.log(answer2);');
add(bundle, 'foo2.js', 'var answer2 = 84; // foo2.js\n');
add(bundle, 'bar2.js', 'console.log(answer2); // bar2.js\n');
return result(bundle, filename);
}

@ -5,12 +5,12 @@ export function test({ assert, preprocessed, js }) {
// sourcemap stores location only for 'answer = 42;'
// not for 'var answer = 42;'
[
[js, 'foo.js', 'answer = 42;'],
[js, 'bar.js', 'console.log(answer);'],
[js, 'foo2.js', 'answer2 = 84;'],
[js, 'bar2.js', 'console.log(answer2);'],
[js, 'foo.js', 'answer = 42;', 4],
[js, 'bar.js', 'console.log(answer);', 0],
[js, 'foo2.js', 'answer2 = 84;', 4],
[js, 'bar2.js', 'console.log(answer2);', 0]
]
.forEach(([where, sourcefile, content]) => {
.forEach(([where, sourcefile, content, column]) => {
assert.deepEqual(
where.mapConsumer.originalPositionFor(
@ -20,7 +20,7 @@ export function test({ assert, preprocessed, js }) {
source: sourcefile,
name: null,
line: 1,
column: 0
column
},
`failed to locate "${content}" from "${sourcefile}"`
);

@ -0,0 +1,58 @@
import MagicString from 'magic-string';
// TODO move util fns to test index.js
function result(filename, src, options = {}) {
const map_fn = options.encodeMappings ? src.generateMap : src.generateDecodedMap;
delete options.encodeMappings;
return {
code: src.toString(),
map: map_fn.apply(src, [{
source: filename,
hires: true,
includeContent: false,
...options
}])
};
}
function replace_all(src, search, replace) {
let idx = src.original.indexOf(search);
if (idx == -1) throw new Error('search not found in src');
do {
src.overwrite(idx, idx + search.length, replace);
} while ((idx = src.original.indexOf(search, idx + 1)) != -1);
}
export default {
js_map_sources: [], // test component has no scripts
preprocess: [
// preprocessor 0
{ markup: ({ content, filename }) => {
const src = new MagicString(content);
replace_all(src, 'replace_me', 'version_1');
return result(filename, src, { encodeMappings: true });
} },
// 1
{ markup: ({ content, filename }) => {
const src = new MagicString(content);
replace_all(src, 'version_1', 'version_2');
return result(filename, src);
} },
// 2
{ markup: ({ content, filename }) => {
const src = new MagicString(content);
replace_all(src, 'version_2', 'version_3');
return result(filename, src, { encodeMappings: true });
} },
// 3
{ markup: ({ content, filename }) => {
const src = new MagicString(content);
replace_all(src, 'version_3', 'version_4');
return result(filename, src);
} }
]
};

@ -0,0 +1,13 @@
export function test({ assert, preprocessed, js }) {
assert.equal(preprocessed.error, undefined);
// TODO can we automate this test?
// we need the output of console.log
// to test the warning message.
// or use a different method for warnings?
// expected warning message:
// warning. svelte.preprocess received encoded sourcemaps (index 0, 2). [....]
}
Loading…
Cancel
Save