diff --git a/.gitignore b/.gitignore
index f7fac04ebaa9..a471c3aaa29c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,10 +18,6 @@ node_modules
/coverage/
/coverage.lcov
/test/*/samples/_
-/test/sourcemaps/samples/*/output.js
-/test/sourcemaps/samples/*/output.js.map
-/test/sourcemaps/samples/*/output.css
-/test/sourcemaps/samples/*/output.css.map
/yarn-error.log
_actual*.*
_output
diff --git a/package-lock.json b/package-lock.json
index 3ddddec68377..e87e2c0b2341 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -4,6 +4,16 @@
"lockfileVersion": 1,
"requires": true,
"dependencies": {
+ "@ampproject/remapping": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-0.3.0.tgz",
+ "integrity": "sha512-dqmASpaTCavldZqwdEpokgG4yOXmEiEGPP3ATTsBbdXXSKf6kx8jt2fPcKhodABdZlYe82OehR2oFK1y9gwZxw==",
+ "dev": true,
+ "requires": {
+ "@jridgewell/resolve-uri": "1.0.0",
+ "sourcemap-codec": "1.4.8"
+ }
+ },
"@babel/code-frame": {
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.1.tgz",
@@ -36,6 +46,12 @@
"integrity": "sha512-KioOCsSvSvXx6xUNLiJz+P+VMb7NRcePjoefOr74Y5P6lEKsiOn35eZyZzgpK4XCNJdXTDR7+zykj0lwxRvZ2g==",
"dev": true
},
+ "@jridgewell/resolve-uri": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-1.0.0.tgz",
+ "integrity": "sha512-9oLAnygRMi8Q5QkYEU4XWK04B+nuoXoxjRvRxgjuChkLZFBja0YPSgdZ7dZtwhncLBcQe/I/E+fLuk5qxcYVJA==",
+ "dev": true
+ },
"@rollup/plugin-commonjs": {
"version": "11.0.0",
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-11.0.0.tgz",
@@ -3737,9 +3753,9 @@
}
},
"sourcemap-codec": {
- "version": "1.4.6",
- "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.6.tgz",
- "integrity": "sha512-1ZooVLYFxC448piVLBbtOxFcXwnymH9oUF8nRd3CuYDVvkRBxRl6pB4Mtas5a4drtL+E8LDgFkQNcgIw6tc8Hg==",
+ "version": "1.4.8",
+ "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
+ "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
"dev": true
},
"spdx-correct": {
diff --git a/package.json b/package.json
index 62b15efb5fab..311c484d5309 100644
--- a/package.json
+++ b/package.json
@@ -56,6 +56,7 @@
},
"homepage": "https://github.com/sveltejs/svelte#README",
"devDependencies": {
+ "@ampproject/remapping": "^0.3.0",
"@rollup/plugin-commonjs": "^11.0.0",
"@rollup/plugin-json": "^4.0.1",
"@rollup/plugin-node-resolve": "^6.0.0",
@@ -89,6 +90,7 @@
"rollup": "^1.27.14",
"source-map": "^0.7.3",
"source-map-support": "^0.5.13",
+ "sourcemap-codec": "^1.4.8",
"tiny-glob": "^0.2.6",
"tslib": "^1.10.0",
"typescript": "^3.5.3"
diff --git a/src/compiler/compile/Component.ts b/src/compiler/compile/Component.ts
index 6c1fe898232f..d3dd8d0a0bf2 100644
--- a/src/compiler/compile/Component.ts
+++ b/src/compiler/compile/Component.ts
@@ -29,7 +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 { 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;
@@ -317,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
];
@@ -330,6 +341,60 @@ export default class Component {
js.map.sourcesContent = [
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) {
+ 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 })'
+ );
+ }
+ }
+ if (css.map) {
+ 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 {
diff --git a/src/compiler/compile/css/Stylesheet.ts b/src/compiler/compile/css/Stylesheet.ts
index dc464d7df806..9d18d642749e 100644
--- a/src/compiler/compile/css/Stylesheet.ts
+++ b/src/compiler/compile/css/Stylesheet.ts
@@ -410,7 +410,7 @@ export default class Stylesheet {
return {
code: code.toString(),
- map: code.generateMap({
+ map: code.generateDecodedMap({
includeContent: true,
source: this.filename,
file
diff --git a/src/compiler/compile/index.ts b/src/compiler/compile/index.ts
index 9d56dfae0252..96a67e2a8d15 100644
--- a/src/compiler/compile/index.ts
+++ b/src/compiler/compile/index.ts
@@ -12,6 +12,7 @@ const valid_options = [
'format',
'name',
'filename',
+ 'sourcemap',
'generate',
'outputFilename',
'cssOutputFilename',
diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts
index ab4ee904e8a8..523141b57b6b 100644
--- a/src/compiler/compile/render_dom/index.ts
+++ b/src/compiler/compile/render_dom/index.ts
@@ -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 = \`\`;`}
const declaration = b`
class ${name} extends @SvelteElement {
constructor(options) {
super();
- ${css.code && b`this.shadowRoot.innerHTML = \`\`;`}
+ ${css.code && b`this.shadowRoot.innerHTML = \`\`;`}
@init(this, { target: this.shadowRoot }, ${definition}, ${has_create_fragment ? 'create_fragment': 'null'}, ${not_equal}, ${prop_indexes}, ${dirty});
diff --git a/src/compiler/compile/render_ssr/index.ts b/src/compiler/compile/render_ssr/index.ts
index ff45abd78b05..62e2d1831476 100644
--- a/src/compiler/compile/render_ssr/index.ts
+++ b/src/compiler/compile/render_ssr/index.ts
@@ -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 = {
diff --git a/src/compiler/interfaces.ts b/src/compiler/interfaces.ts
index 6e96e340adee..263f25d7d759 100644
--- a/src/compiler/interfaces.ts
+++ b/src/compiler/interfaces.ts
@@ -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;
@@ -110,6 +112,7 @@ export interface CompileOptions {
filename?: string;
generate?: 'dom' | 'ssr' | false;
+ sourcemap?: object | string;
outputFilename?: string;
cssOutputFilename?: string;
sveltePath?: string;
@@ -165,5 +168,5 @@ export interface Var {
export interface CssResult {
code: string;
- map: SourceMap;
+ map: DecodedSourceMap;
}
diff --git a/src/compiler/preprocess/index.ts b/src/compiler/preprocess/index.ts
index 5371baf4b3cd..30f4d08f1b23 100644
--- a/src/compiler/preprocess/index.ts
+++ b/src/compiler/preprocess/index.ts
@@ -1,6 +1,11 @@
+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, combine_sourcemaps, combine_sourcemaps_map_stats } from '../utils/string_with_sourcemap';
+
export interface Processed {
code: string;
- map?: object | string;
+ map?: SourceMapInput;
dependencies?: string[];
}
@@ -37,12 +42,18 @@ function parse_attributes(str: string) {
interface Replacement {
offset: number;
length: number;
- replacement: string;
+ replacement: StringWithSourcemap;
}
-async function replace_async(str: string, re: RegExp, func: (...any) => Promise) {
+async function replace_async(
+ filename: string,
+ source: string,
+ get_location: ReturnType,
+ re: RegExp,
+ func: (...any) => Promise
+): Promise {
const replacements: Array> = [];
- str.replace(re, (...args) => {
+ source.replace(re, (...args) => {
replacements.push(
func(...args).then(
res =>
@@ -55,79 +66,200 @@ async function replace_async(str: string, re: RegExp, func: (...any) => Promise<
);
return '';
});
- let out = '';
+ const out = new StringWithSourcemap();
let last_end = 0;
for (const { offset, length, replacement } of await Promise.all(
replacements
)) {
- out += str.slice(last_end, offset) + replacement;
+ // content = source before replacement
+ const content = StringWithSourcemap.from_source(
+ filename, source.slice(last_end, offset), get_location(last_end));
+ out.concat(content).concat(replacement);
last_end = offset + length;
}
- out += str.slice(last_end);
- return out;
+ // final_content = source after last replacement
+ const final_content = StringWithSourcemap.from_source(
+ filename, source.slice(last_end), get_location(last_end));
+ return out.concat(final_content);
+}
+
+function get_replacement(
+ filename: string,
+ offset: number,
+ get_location: ReturnType,
+ original: string,
+ processed: Processed,
+ prefix: string,
+ suffix: string
+): StringWithSourcemap {
+ const prefix_with_map = StringWithSourcemap.from_source(
+ filename, prefix, get_location(offset));
+ const suffix_with_map = StringWithSourcemap.from_source(
+ filename, suffix, get_location(offset + prefix.length + original.length));
+
+ let decoded_map;
+ if (processed.map) {
+ decoded_map = typeof processed.map === "string" ? JSON.parse(processed.map) : processed.map;
+ if (typeof(decoded_map.mappings) === 'string')
+ decoded_map.mappings = decode_mappings(decoded_map.mappings);
+ sourcemap_add_offset(decoded_map, get_location(offset + prefix.length));
+ }
+ const processed_with_map = StringWithSourcemap.from_processed(processed.code, decoded_map);
+
+ return prefix_with_map.concat(processed_with_map).concat(suffix_with_map);
}
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 = Array.isArray(preprocessor) ? preprocessor : [preprocessor];
+ const preprocessors = preprocessor
+ ? Array.isArray(preprocessor) ? preprocessor : [preprocessor]
+ : []; // noop
const markup = preprocessors.map(p => p.markup).filter(Boolean);
const script = preprocessors.map(p => p.script).filter(Boolean);
const style = preprocessors.map(p => p.style).filter(Boolean);
+ // 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: Array = [];
+
+ // 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) {
+
+ // run markup preprocessor
const processed = await fn({
content: source,
filename
});
+
if (processed && processed.dependencies) dependencies.push(...processed.dependencies);
source = processed ? processed.code : source;
+ if (processed && processed.map)
+ sourcemap_list.unshift(
+ typeof(processed.map) === 'string'
+ ? JSON.parse(processed.map) as RawSourceMap
+ : processed.map as (RawSourceMap | DecodedSourceMap)
+ );
}
+ // TODO run script and style in parallel
+
for (const fn of script) {
- source = await replace_async(
+ const get_location = getLocator(source);
+ const res = await replace_async(
+ filename,
source,
+ get_location,
/|` : match;
+ return processed
+ ? get_replacement(filename, offset, get_location, content, processed, ``)
+ : no_change();
}
);
+ source = res.string;
+ sourcemap_list.unshift(res.map);
}
for (const fn of style) {
- source = await replace_async(
+ const get_location = getLocator(source);
+ const res = await replace_async(
+ filename,
source,
+ get_location,
/|` : match;
+ return processed
+ ? get_replacement(filename, offset, get_location, content, processed, ``)
+ : no_change();
}
);
+ source = res.string;
+ sourcemap_list.unshift(res.map);
+ }
+
+ 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_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 {
@@ -138,6 +270,7 @@ export default async function preprocess(
code: source,
dependencies: [...new Set(dependencies)],
+ map,
toString() {
return source;
diff --git a/src/compiler/utils/string_with_sourcemap.ts b/src/compiler/utils/string_with_sourcemap.ts
new file mode 100644
index 000000000000..0e0843609241
--- /dev/null
+++ b/src/compiler/utils/string_with_sourcemap.ts
@@ -0,0 +1,333 @@
+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;
+ column: number;
+};
+
+function last_line_length(s: string) {
+ return s.length - s.lastIndexOf('\n') - 1;
+}
+
+// mutate map in-place
+export function sourcemap_add_offset(
+ map: DecodedSourceMap, offset: SourceLocation
+) {
+ // shift columns in first line
+ const m = map.mappings;
+ m[0].forEach(seg => {
+ if (seg[3]) seg[3] += offset.column;
+ });
+ // shift lines
+ m.forEach(line => {
+ line.forEach(seg => {
+ if (seg[2]) seg[2] += offset.line;
+ });
+ });
+}
+
+function merge_tables(this_table: T[], other_table): [T[], number[], boolean, boolean] {
+ const new_table = this_table.slice();
+ const idx_map = [];
+ other_table = other_table || [];
+ let val_changed = false;
+ for (const [other_idx, other_val] of other_table.entries()) {
+ const this_idx = this_table.indexOf(other_val);
+ if (this_idx >= 0) {
+ idx_map[other_idx] = this_idx;
+ } else {
+ const new_idx = new_table.length;
+ new_table[new_idx] = other_val;
+ idx_map[other_idx] = new_idx;
+ val_changed = true;
+ }
+ }
+ let idx_changed = val_changed;
+ if (val_changed) {
+ if (idx_map.find((val, idx) => val != idx) === undefined) {
+ // idx_map is identity map [0, 1, 2, 3, 4, ....]
+ idx_changed = false;
+ }
+ }
+ return [new_table, idx_map, val_changed, idx_changed];
+}
+
+function pushArray(_this: T[], other: T[]) {
+ for (let i = 0; i < other.length; i++)
+ _this.push(other[i]);
+}
+
+export class StringWithSourcemap {
+ string: string;
+ map: DecodedSourceMap;
+
+ constructor(string = '', map = null) {
+ this.string = string;
+ if (map)
+ this.map = map as DecodedSourceMap;
+ else
+ this.map = {
+ version: 3,
+ mappings: [],
+ sources: [],
+ names: []
+ };
+ }
+
+ // concat in-place (mutable), return this (chainable)
+ // will also mutate the `other` object
+ concat(other: StringWithSourcemap): StringWithSourcemap {
+ // noop: if one is empty, return the other
+ if (other.string == '') return this;
+ if (this.string == '') {
+ this.string = other.string;
+ this.map = other.map;
+ return this;
+ }
+
+ this.string += other.string;
+
+ const m1 = this.map;
+ const m2 = other.map;
+
+ // combine sources and names
+ const [sources, new_source_idx, sources_changed, sources_idx_changed] = merge_tables(m1.sources, m2.sources);
+ const [names, new_name_idx, names_changed, names_idx_changed] = merge_tables(m1.names, m2.names);
+
+ if (sources_changed) m1.sources = sources;
+ if (names_changed) m1.names = names;
+
+ // unswitched loops are faster
+ if (sources_idx_changed && names_idx_changed) {
+ m2.mappings.forEach(line => {
+ line.forEach(seg => {
+ if (seg[1]) seg[1] = new_source_idx[seg[1]];
+ if (seg[4]) seg[4] = new_name_idx[seg[4]];
+ });
+ });
+ } else if (sources_idx_changed) {
+ m2.mappings.forEach(line => {
+ line.forEach(seg => {
+ if (seg[1]) seg[1] = new_source_idx[seg[1]];
+ });
+ });
+ } else if (names_idx_changed) {
+ m2.mappings.forEach(line => {
+ line.forEach(seg => {
+ if (seg[4]) seg[4] = new_name_idx[seg[4]];
+ });
+ });
+ }
+
+ // combine the mappings
+
+ // combine
+ // 1. last line of first map
+ // 2. first line of second map
+ // columns of 2 must be shifted
+
+ const column_offset = last_line_length(this.string);
+ if (m2.mappings.length > 0 && column_offset > 0) {
+ // shift columns in first line
+ m2.mappings[0].forEach(seg => {
+ seg[0] += column_offset;
+ });
+ }
+
+ // combine last line + first line
+ pushArray(m1.mappings[m1.mappings.length - 1], m2.mappings.shift());
+
+ // append other lines
+ pushArray(m1.mappings, m2.mappings);
+
+ return this;
+ }
+
+ static from_processed(string: string, map?: DecodedSourceMap): StringWithSourcemap {
+ if (map) return new StringWithSourcemap(string, map);
+ map = { version: 3, names: [], sources: [], mappings: [] };
+ if (string == '') return new StringWithSourcemap(string, map);
+ // add empty SourceMapSegment[] for every line
+ const lineCount = string.split('\n').length;
+ map.mappings = Array.from({length: lineCount}).map(_ => []);
+ return new StringWithSourcemap(string, map);
+ }
+
+ static from_source(
+ source_file: string, source: string, offset_in_source?: SourceLocation
+ ): StringWithSourcemap {
+ const offset = offset_in_source || { line: 0, column: 0 };
+ const map: DecodedSourceMap = { version: 3, names: [], sources: [source_file], mappings: [] };
+ if (source.length == 0) return new StringWithSourcemap(source, map);
+
+ // we create a high resolution identity map here,
+ // we know that it will eventually be merged with svelte's map,
+ // at which stage the resolution will decrease.
+ map.mappings = source.split("\n").map((line, line_idx) => {
+ let pos = 0;
+ const segs = line.split(/([^\d\w\s]|\s+)/g)
+ .filter(s => s !== "").map(s => {
+ const seg: SourceMapSegment = [
+ pos, 0,
+ line_idx + offset.line,
+ pos + (line_idx == 0 ? offset.column : 0) // shift first line
+ ];
+ pos = pos + s.length;
+ return seg;
+ });
+ return segs;
+ });
+ 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,
+ 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());
+ }
+ }
+ });
+}
diff --git a/test/preprocess/index.js b/test/preprocess/index.js
index 5d83bb60590d..09ab03fbbd6c 100644
--- a/test/preprocess/index.js
+++ b/test/preprocess/index.js
@@ -8,17 +8,25 @@ describe('preprocess', () => {
const config = loadConfig(`${__dirname}/samples/${dir}/_config.js`);
const solo = config.solo || /\.solo/.test(dir);
+ const skip = config.skip || /\.skip/.test(dir);
if (solo && process.env.CI) {
throw new Error('Forgot to remove `solo: true` from test');
}
- (config.skip ? it.skip : solo ? it.only : it)(dir, async () => {
+ (skip ? it.skip : solo ? it.only : it)(dir, async () => {
const input = fs.readFileSync(`${__dirname}/samples/${dir}/input.svelte`, 'utf-8');
const expected = fs.readFileSync(`${__dirname}/samples/${dir}/output.svelte`, 'utf-8');
- const result = await svelte.preprocess(input, config.preprocess);
+ const result = await svelte.preprocess(
+ input,
+ config.preprocess,
+ config.options || { filename: 'input.svelte' }
+ );
fs.writeFileSync(`${__dirname}/samples/${dir}/_actual.html`, result.code);
+ if (result.map) {
+ fs.writeFileSync(`${__dirname}/samples/${dir}/_actual.html.map`, JSON.stringify(result.map, null, 2));
+ }
assert.equal(result.code, expected);
diff --git a/test/preprocess/samples/filename/_config.js b/test/preprocess/samples/filename/_config.js
index c71cdafcac9a..1563ac71fd7a 100644
--- a/test/preprocess/samples/filename/_config.js
+++ b/test/preprocess/samples/filename/_config.js
@@ -1,6 +1,7 @@
export default {
preprocess: {
- filename: 'file.svelte',
+ // this is ignored cos filename is set in options
+ //filename: 'file.svelte',
markup: ({ content, filename }) => {
return {
code: content.replace('__MARKUP_FILENAME__', filename)
@@ -16,5 +17,10 @@ export default {
code: content.replace('__SCRIPT_FILENAME__', filename)
};
}
+ },
+ options: {
+ // options.filename is preferred over preprocessor.filename
+ // see function preprocess
+ filename: 'file.svelte'
}
-};
\ No newline at end of file
+};
diff --git a/test/setup.js b/test/setup.js
index 7406a07dd9fb..14e0366387b1 100644
--- a/test/setup.js
+++ b/test/setup.js
@@ -8,22 +8,28 @@ require.extensions['.js'] = function(module, filename) {
const exports = [];
let code = fs.readFileSync(filename, 'utf-8')
- .replace(/^import \* as (\w+) from ['"]([^'"]+)['"];?/gm, 'var $1 = require("$2");')
- .replace(/^import (\w+) from ['"]([^'"]+)['"];?/gm, 'var {default: $1} = require("$2");')
- .replace(/^import {([^}]+)} from ['"](.+)['"];?/gm, 'var {$1} = require("$2");')
- .replace(/^export default /gm, 'exports.default = ')
- .replace(/^export (const|let|var|class|function) (\w+)/gm, (match, type, name) => {
+ .replace(/^import\s+\*\s+as\s+(\w+)\s+from\s+(['"])(.+?)\2;?/gm, 'var $1 = require($2$3$2);')
+ .replace(/^import\s+(\w+)\s+from\s+(['"])(.+?)\2;?/gm, 'var {default: $1} = require($2$3$2);')
+ .replace(/^import\s+(?:(\w+)\s*,\s*)?{([^}]+)}(?:\s*,\s*(\w+))?\s+from\s+(['"])(.+?)\4;?/gm,
+ (match, default_name_1, names, default_name_2, quote, source) => {
+ names = names.replace(/\s+as\s+/g, ': ');
+ let default_name = default_name_1 || default_name_2;
+ default_name = default_name ? `default: ${default_name}, ` : '';
+ return `var {${default_name}${names}} = require(${quote}${source}${quote});`;
+ })
+ .replace(/^export\s+default\s+/gm, 'exports.default = ')
+ .replace(/^export\s+(const|let|var|class|function)\s+(\w+)/gm, (match, type, name) => {
exports.push(name);
return `${type} ${name}`;
})
- .replace(/^export \{([^}]+)\}(?: from ['"]([^'"]+)['"];?)?/gm, (match, names, source) => {
+ .replace(/^export\s+\{([^}]+)\}(?:\s+from\s+(['"])(.+?)\2;?)?/gm, (match, names, quote, source) => {
names.split(',').filter(Boolean).forEach(name => {
exports.push(name);
});
- return source ? `const { ${names} } = require("${source}");` : '';
+ return source ? `const { ${names} } = require(${quote}${source}${quote});` : '';
})
- .replace(/^export function (\w+)/gm, 'exports.$1 = function $1');
+ .replace(/^export\s+function\s+(\w+)/gm, 'exports.$1 = function $1');
exports.forEach(name => {
code += `\nexports.${name} = ${name};`;
diff --git a/test/sourcemaps/index.js b/test/sourcemaps/index.js
index 0b0424a764ff..b8ba4aaa2a39 100644
--- a/test/sourcemaps/index.js
+++ b/test/sourcemaps/index.js
@@ -1,73 +1,131 @@
import * as fs from "fs";
import * as path from "path";
import * as assert from "assert";
-import { svelte } from "../helpers.js";
+import { loadConfig, svelte } from "../helpers.js";
+// keep source-map at version 0.7.x
+// 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 => {
if (dir[0] === ".") return;
+ const config = loadConfig(`${__dirname}/samples/${dir}/_config.js`);
+
// add .solo to a sample directory name to only run that test
- const solo = /\.solo/.test(dir);
- const skip = /\.skip/.test(dir);
+ const solo = config.solo || /\.solo/.test(dir);
+ const skip = config.skip || /\.skip/.test(dir);
if (solo && process.env.CI) {
throw new Error("Forgot to remove `solo: true` from test");
}
(solo ? it.only : skip ? it.skip : it)(dir, async () => {
- const filename = path.resolve(
- `${__dirname}/samples/${dir}/input.svelte`
- );
- const outputFilename = path.resolve(
- `${__dirname}/samples/${dir}/output`
- );
+ const { test } = require(`./samples/${dir}/test.js`);
+ const inputFile = path.resolve(`${__dirname}/samples/${dir}/input.svelte`);
+ const outputName = '_actual';
+ const outputBase = path.resolve(`${__dirname}/samples/${dir}/${outputName}`);
+
+ const input = {};
+ input.code = fs.readFileSync(inputFile, "utf-8");
+ input.locate = getLocator(input.code);
- const input = fs.readFileSync(filename, "utf-8").replace(/\s+$/, "");
- const { js, css } = svelte.compile(input, {
- filename,
- outputFilename: `${outputFilename}.js`,
- cssOutputFilename: `${outputFilename}.css`
+ let preprocessed;
+ try {
+ preprocessed = await svelte.preprocess(
+ input.code,
+ config.preprocess, {
+ filename: "input.svelte"
+ });
+ } catch (error) {
+ preprocessed = { error };
+ // run test without js, css
+ 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",
+ sourcemap: preprocessed.map,
+ // filenames for sourcemaps
+ outputFilename: `${outputName}.js`,
+ cssOutputFilename: `${outputName}.css`,
});
- const _code = js.code.replace(/Svelte v\d+\.\d+\.\d+/, match => match.replace(/\d/g, 'x'));
+ js.code = js.code.replace(
+ /generated by Svelte v\d+\.\d+\.\d+/,
+ match => match.replace(/\d/g, "x")
+ );
+ fs.writeFileSync(`${outputBase}.svelte`, preprocessed.code);
+ if (preprocessed.map) {
+ fs.writeFileSync(
+ `${outputBase}.svelte.map`,
+ // TODO encode mappings for output - svelte.preprocess returns decoded mappings
+ JSON.stringify(preprocessed.map, null, 2)
+ );
+ }
fs.writeFileSync(
- `${outputFilename}.js`,
- `${_code}\n//# sourceMappingURL=output.js.map`
+ `${outputBase}.js`,
+ `${js.code}\n//# sourceMappingURL=${outputName}.js.map`
);
fs.writeFileSync(
- `${outputFilename}.js.map`,
- JSON.stringify(js.map, null, " ")
+ `${outputBase}.js.map`,
+ JSON.stringify(js.map, 0, 2)
);
-
if (css.code) {
fs.writeFileSync(
- `${outputFilename}.css`,
- `${css.code}\n/*# sourceMappingURL=output.css.map */`
+ `${outputBase}.css`,
+ `${css.code}\n/*# sourceMappingURL=${outputName}.css.map */`
);
fs.writeFileSync(
- `${outputFilename}.css.map`,
- JSON.stringify(css.map, null, " ")
+ `${outputBase}.css.map`,
+ JSON.stringify(css.map, 0, 2)
);
}
- assert.deepEqual(js.map.sources, ["input.svelte"]);
- if (css.map) assert.deepEqual(css.map.sources, ["input.svelte"]);
+ assert.deepEqual(
+ js.map.sources.slice().sort(),
+ (config.js_map_sources || ["input.svelte"]).sort()
+ );
+ if (css.map) {
+ assert.deepEqual(
+ css.map.sources.slice().sort(),
+ (config.css_map_sources || ["input.svelte"]).sort()
+ );
+ };
- const { test } = require(`./samples/${dir}/test.js`);
+ // 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
- const locateInSource = getLocator(input);
+ preprocessed.mapConsumer = preprocessed.map && await new SourceMapConsumer(preprocessed.map);
+ preprocessed.locate = getLocator(preprocessed.code);
+ preprocessed.locate_1 = getLocator(preprocessed.code, { offsetLine: 1 });
- const smc = await new SourceMapConsumer(js.map);
- const locateInGenerated = getLocator(_code);
+ js.mapConsumer = js.map && await new SourceMapConsumer(js.map);
+ js.locate = getLocator(js.code);
+ js.locate_1 = getLocator(js.code, { offsetLine: 1 });
- const smcCss = css.map && await new SourceMapConsumer(css.map);
- const locateInGeneratedCss = getLocator(css.code || '');
+ css.mapConsumer = css.map && await new SourceMapConsumer(css.map);
+ css.locate = getLocator(css.code || '');
+ css.locate_1 = getLocator(css.code || '', { offsetLine: 1 });
- test({ assert, code: _code, map: js.map, smc, smcCss, locateInSource, locateInGenerated, locateInGeneratedCss });
+ test({ assert, input, preprocessed, js, css });
});
});
});
diff --git a/test/sourcemaps/samples/basic/test.js b/test/sourcemaps/samples/basic/test.js
index 705976167461..34c619c128ca 100644
--- a/test/sourcemaps/samples/basic/test.js
+++ b/test/sourcemaps/samples/basic/test.js
@@ -1,12 +1,12 @@
-export function test({ assert, smc, locateInSource, locateInGenerated }) {
- const expected = locateInSource('foo.bar.baz');
+export function test({ assert, input, js }) {
+ const expected = input.locate('foo.bar.baz');
let start;
let actual;
- start = locateInGenerated('ctx[0].bar.baz');
+ start = js.locate('ctx[0].bar.baz');
- actual = smc.originalPositionFor({
+ actual = js.mapConsumer.originalPositionFor({
line: start.line + 1,
column: start.column
});
@@ -18,9 +18,9 @@ export function test({ assert, smc, locateInSource, locateInGenerated }) {
column: expected.column
});
- start = locateInGenerated('ctx[0].bar.baz', start.character + 1);
+ start = js.locate('ctx[0].bar.baz', start.character + 1);
- actual = smc.originalPositionFor({
+ actual = js.mapConsumer.originalPositionFor({
line: start.line + 1,
column: start.column
});
diff --git a/test/sourcemaps/samples/binding-shorthand.skip/test.js b/test/sourcemaps/samples/binding-shorthand.skip/test.js
index cdfbbdc091ac..13ecdbf88934 100644
--- a/test/sourcemaps/samples/binding-shorthand.skip/test.js
+++ b/test/sourcemaps/samples/binding-shorthand.skip/test.js
@@ -1,13 +1,14 @@
-export function test({ assert, smc, locateInSource, locateInGenerated }) {
- const expected = locateInSource('potato');
+export function test({ assert, input, js }) {
+ const expected = input.locate('potato');
let start;
- start = locateInGenerated('potato');
- start = locateInGenerated('potato', start.character + 1);
- start = locateInGenerated('potato', start.character + 1); // we need the third instance of 'potato'
+ start = js.locate('potato');
+ start = js.locate('potato', start.character + 1);
+ start = js.locate('potato', start.character + 1);
+ // we need the third instance of 'potato'
- const actual = smc.originalPositionFor({
+ const actual = js.mapConsumer.originalPositionFor({
line: start.line + 1,
column: start.column
});
diff --git a/test/sourcemaps/samples/binding/test.js b/test/sourcemaps/samples/binding/test.js
index 31c0e98442d5..3cb3246e5089 100644
--- a/test/sourcemaps/samples/binding/test.js
+++ b/test/sourcemaps/samples/binding/test.js
@@ -1,12 +1,12 @@
-export function test({ assert, smc, locateInSource, locateInGenerated }) {
- const expected = locateInSource('bar.baz');
+export function test({ assert, input, js }) {
+ const expected = input.locate('bar.baz');
let start;
let actual;
- start = locateInGenerated('bar.baz');
+ start = js.locate('bar.baz');
- actual = smc.originalPositionFor({
+ actual = js.mapConsumer.originalPositionFor({
line: start.line + 1,
column: start.column
});
@@ -18,9 +18,9 @@ export function test({ assert, smc, locateInSource, locateInGenerated }) {
column: expected.column
});
- start = locateInGenerated('bar.baz', start.character + 1);
+ start = js.locate('bar.baz', start.character + 1);
- actual = smc.originalPositionFor({
+ actual = js.mapConsumer.originalPositionFor({
line: start.line + 1,
column: start.column
});
diff --git a/test/sourcemaps/samples/css/input.svelte b/test/sourcemaps/samples/css/input.svelte
index ad0845d15b73..eba8f758a719 100644
--- a/test/sourcemaps/samples/css/input.svelte
+++ b/test/sourcemaps/samples/css/input.svelte
@@ -4,4 +4,4 @@
.foo {
color: red;
}
-
\ No newline at end of file
+
diff --git a/test/sourcemaps/samples/css/test.js b/test/sourcemaps/samples/css/test.js
index 54082bd4ee66..1e0dda1dff43 100644
--- a/test/sourcemaps/samples/css/test.js
+++ b/test/sourcemaps/samples/css/test.js
@@ -1,14 +1,14 @@
-export function test({ assert, smcCss, locateInSource, locateInGeneratedCss }) {
- const expected = locateInSource( '.foo' );
+export function test({ assert, input, css }) {
+ const expected = input.locate('.foo');
- const start = locateInGeneratedCss( '.foo' );
+ const start = css.locate('.foo');
- const actual = smcCss.originalPositionFor({
+ const actual = css.mapConsumer.originalPositionFor({
line: start.line + 1,
column: start.column
});
- assert.deepEqual( actual, {
+ assert.deepEqual(actual, {
source: 'input.svelte',
name: null,
line: expected.line + 1,
diff --git a/test/sourcemaps/samples/decoded-sourcemap/_config.js b/test/sourcemaps/samples/decoded-sourcemap/_config.js
new file mode 100644
index 000000000000..fc4d2a03c90e
--- /dev/null
+++ b/test/sourcemaps/samples/decoded-sourcemap/_config.js
@@ -0,0 +1,32 @@
+import MagicString from 'magic-string';
+
+function replace(search, replace, content, src, options = {}) {
+ let idx = -1;
+ while ((idx = content.indexOf(search, idx + 1)) != -1) {
+ src.overwrite(idx, idx + search.length, replace, options);
+ }
+}
+
+function result(src, filename) {
+ return {
+ code: src.toString(),
+ map: src.generateDecodedMap({ // return decoded sourcemap
+ source: filename,
+ hires: true,
+ includeContent: false
+ })
+ };
+}
+
+export default {
+
+ js_map_sources: [], // test component has no scripts
+
+ preprocess: {
+ markup: ({ content, filename }) => {
+ const src = new MagicString(content);
+ replace('replace me', 'success', content, src);
+ return result(src, filename);
+ }
+ }
+};
diff --git a/test/sourcemaps/samples/decoded-sourcemap/input.svelte b/test/sourcemaps/samples/decoded-sourcemap/input.svelte
new file mode 100644
index 000000000000..b233d7f670d7
--- /dev/null
+++ b/test/sourcemaps/samples/decoded-sourcemap/input.svelte
@@ -0,0 +1,2 @@
+decoded-sourcemap
+replace me
diff --git a/test/sourcemaps/samples/decoded-sourcemap/test.js b/test/sourcemaps/samples/decoded-sourcemap/test.js
new file mode 100644
index 000000000000..54d930cb9714
--- /dev/null
+++ b/test/sourcemaps/samples/decoded-sourcemap/test.js
@@ -0,0 +1,19 @@
+export function test({ assert, input, preprocessed }) {
+
+ const expected = input.locate('replace me');
+
+ const start = preprocessed.locate('success');
+
+ const actualbar = preprocessed.mapConsumer.originalPositionFor({
+ line: start.line + 1,
+ column: start.column
+ });
+
+ assert.deepEqual(actualbar, {
+ source: 'input.svelte',
+ name: null,
+ line: expected.line + 1,
+ column: expected.column
+ });
+
+}
diff --git a/test/sourcemaps/samples/detect-lowres-sourcemaps/_config.js b/test/sourcemaps/samples/detect-lowres-sourcemaps/_config.js
new file mode 100644
index 000000000000..b11594dd855f
--- /dev/null
+++ b/test/sourcemaps/samples/detect-lowres-sourcemaps/_config.js
@@ -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 });
+ } }
+ ]
+
+};
diff --git a/test/sourcemaps/samples/detect-lowres-sourcemaps/input.svelte b/test/sourcemaps/samples/detect-lowres-sourcemaps/input.svelte
new file mode 100644
index 000000000000..2b3afd881b3f
--- /dev/null
+++ b/test/sourcemaps/samples/detect-lowres-sourcemaps/input.svelte
@@ -0,0 +1,10 @@
+replace_me
+replace_me
+replace_me
+replace_me
+replace_me
+replace_me
+replace_me
+replace_me
+replace_me
+replace_me
diff --git a/test/sourcemaps/samples/detect-lowres-sourcemaps/test.js b/test/sourcemaps/samples/detect-lowres-sourcemaps/test.js
new file mode 100644
index 000000000000..0f63efb358d0
--- /dev/null
+++ b/test/sourcemaps/samples/detect-lowres-sourcemaps/test.js
@@ -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?
+
+}
diff --git a/test/sourcemaps/samples/each-block/input.svelte b/test/sourcemaps/samples/each-block/input.svelte
index bf2f0609bd7e..bf33a18b0d94 100644
--- a/test/sourcemaps/samples/each-block/input.svelte
+++ b/test/sourcemaps/samples/each-block/input.svelte
@@ -1,3 +1,3 @@
{#each foo as bar}
{bar}
-{/each}
\ No newline at end of file
+{/each}
diff --git a/test/sourcemaps/samples/each-block/test.js b/test/sourcemaps/samples/each-block/test.js
index 35479986a546..08b37686a462 100644
--- a/test/sourcemaps/samples/each-block/test.js
+++ b/test/sourcemaps/samples/each-block/test.js
@@ -1,10 +1,10 @@
-export function test({ assert, code, smc, map, locateInSource, locateInGenerated }) {
- const startIndex = code.indexOf('create_main_fragment');
+export function test({ assert, input, js }) {
+ const startIndex = js.code.indexOf('create_main_fragment');
- const expected = locateInSource('each');
- const start = locateInGenerated('length', startIndex);
+ const expected = input.locate('each');
+ const start = js.locate('length', startIndex);
- const actual = smc.originalPositionFor({
+ const actual = js.mapConsumer.originalPositionFor({
line: start.line + 1,
column: start.column
});
diff --git a/test/sourcemaps/samples/preprocessed-markup/_config.js b/test/sourcemaps/samples/preprocessed-markup/_config.js
new file mode 100644
index 000000000000..cb3eb90e01c6
--- /dev/null
+++ b/test/sourcemaps/samples/preprocessed-markup/_config.js
@@ -0,0 +1,18 @@
+import MagicString from 'magic-string';
+
+export default {
+ preprocess: {
+ markup: ({ content, filename }) => {
+ const src = new MagicString(content);
+ const idx = content.indexOf("baritone");
+ src.overwrite(idx, idx+"baritone".length, "bar");
+ return {
+ code: src.toString(),
+ map: src.generateDecodedMap({
+ source: filename,
+ includeContent: false
+ })
+ };
+ }
+ }
+};
diff --git a/test/sourcemaps/samples/preprocessed-markup/input.svelte b/test/sourcemaps/samples/preprocessed-markup/input.svelte
new file mode 100644
index 000000000000..ee4b90372acd
--- /dev/null
+++ b/test/sourcemaps/samples/preprocessed-markup/input.svelte
@@ -0,0 +1,5 @@
+
+
+{foo.baritone.baz}
diff --git a/test/sourcemaps/samples/preprocessed-markup/test.js b/test/sourcemaps/samples/preprocessed-markup/test.js
new file mode 100644
index 000000000000..9c3f0ef06d6c
--- /dev/null
+++ b/test/sourcemaps/samples/preprocessed-markup/test.js
@@ -0,0 +1,32 @@
+export function test({ assert, input, js }) {
+ const expectedBar = input.locate('baritone.baz');
+ const expectedBaz = input.locate('.baz');
+
+ let start = js.locate('bar.baz');
+
+ const actualbar = js.mapConsumer.originalPositionFor({
+ line: start.line + 1,
+ column: start.column
+ });
+
+ assert.deepEqual(actualbar, {
+ source: 'input.svelte',
+ name: null,
+ line: expectedBar.line + 1,
+ column: expectedBar.column
+ });
+
+ start = js.locate('.baz');
+
+ const actualbaz = js.mapConsumer.originalPositionFor({
+ line: start.line + 1,
+ column: start.column
+ });
+
+ assert.deepEqual(actualbaz, {
+ source: 'input.svelte',
+ name: null,
+ line: expectedBaz.line + 1,
+ column: expectedBaz.column
+ });
+}
diff --git a/test/sourcemaps/samples/preprocessed-multiple/_config.js b/test/sourcemaps/samples/preprocessed-multiple/_config.js
new file mode 100644
index 000000000000..d4c1e6cdbeb5
--- /dev/null
+++ b/test/sourcemaps/samples/preprocessed-multiple/_config.js
@@ -0,0 +1,48 @@
+import MagicString from 'magic-string';
+
+export default {
+ preprocess: {
+ markup: ({ content, filename }) => {
+ const src = new MagicString(content);
+ const idx = content.indexOf("baritone");
+ src.overwrite(idx, idx + "baritone".length, "bar");
+
+ const css_idx = content.indexOf("--bazitone");
+ src.overwrite(css_idx, css_idx + "--bazitone".length, "--baz");
+ return {
+ code: src.toString(),
+ map: src.generateDecodedMap({
+ source: filename,
+ hires: true,
+ includeContent: false
+ })
+ };
+ },
+ script: ({ content, filename }) => {
+ const src = new MagicString(content);
+ const idx = content.indexOf("bar");
+ src.prependLeft(idx, " ");
+ return {
+ code: src.toString(),
+ map: src.generateDecodedMap({
+ source: filename,
+ hires: true,
+ includeContent: false
+ })
+ };
+ },
+ style: ({ content, filename }) => {
+ const src = new MagicString(content);
+ const idx = content.indexOf("--baz");
+ src.prependLeft(idx, " ");
+ return {
+ code: src.toString(),
+ map: src.generateDecodedMap({
+ source: filename,
+ hires: true,
+ includeContent: false
+ })
+ };
+ }
+ }
+};
diff --git a/test/sourcemaps/samples/preprocessed-multiple/input.svelte b/test/sourcemaps/samples/preprocessed-multiple/input.svelte
new file mode 100644
index 000000000000..e656d399ae04
--- /dev/null
+++ b/test/sourcemaps/samples/preprocessed-multiple/input.svelte
@@ -0,0 +1,9 @@
+
+
+multiple {foo}
diff --git a/test/sourcemaps/samples/preprocessed-multiple/test.js b/test/sourcemaps/samples/preprocessed-multiple/test.js
new file mode 100644
index 000000000000..64b215677306
--- /dev/null
+++ b/test/sourcemaps/samples/preprocessed-multiple/test.js
@@ -0,0 +1,37 @@
+export function test({ assert, input, js, css }) {
+ const expectedBar = input.locate('baritone');
+ const expectedBaz = input.locate('--bazitone');
+
+ let start = js.locate('bar');
+
+ const actualbar = js.mapConsumer.originalPositionFor({
+ line: start.line + 1,
+ column: start.column
+ });
+
+ assert.deepEqual(actualbar, {
+ source: 'input.svelte',
+ name: null,
+ line: expectedBar.line + 1,
+ column: expectedBar.column
+ });
+
+ start = css.locate('--baz');
+
+ const actualbaz = css.mapConsumer.originalPositionFor({
+ line: start.line + 1,
+ column: start.column
+ });
+
+ assert.deepEqual(actualbaz, {
+ source: 'input.svelte',
+ name: null,
+ line: expectedBaz.line + 1,
+ column: expectedBaz.column
+ }, `\
+couldn't find baz in css,
+ gen: ${JSON.stringify(start)}
+ actual: ${JSON.stringify(actualbaz)}
+ expected: ${JSON.stringify(expectedBaz)}\
+`);
+}
diff --git a/test/sourcemaps/samples/preprocessed-script/_config.js b/test/sourcemaps/samples/preprocessed-script/_config.js
new file mode 100644
index 000000000000..8781e9d7f2c1
--- /dev/null
+++ b/test/sourcemaps/samples/preprocessed-script/_config.js
@@ -0,0 +1,19 @@
+import MagicString from 'magic-string';
+
+export default {
+ preprocess: {
+ script: ({ content, filename }) => {
+ const src = new MagicString(content);
+ const idx = content.indexOf("baritone");
+ src.overwrite(idx, idx+"baritone".length, "bar");
+ return {
+ code: src.toString(),
+ map: src.generateMap({
+ source: filename,
+ hires: true,
+ includeContent: false
+ })
+ };
+ }
+ }
+};
diff --git a/test/sourcemaps/samples/preprocessed-script/input.svelte b/test/sourcemaps/samples/preprocessed-script/input.svelte
new file mode 100644
index 000000000000..11586619e1a0
--- /dev/null
+++ b/test/sourcemaps/samples/preprocessed-script/input.svelte
@@ -0,0 +1,9 @@
+
+
+{foo.bar.baz}
diff --git a/test/sourcemaps/samples/preprocessed-script/test.js b/test/sourcemaps/samples/preprocessed-script/test.js
new file mode 100644
index 000000000000..a7e53a96e70b
--- /dev/null
+++ b/test/sourcemaps/samples/preprocessed-script/test.js
@@ -0,0 +1,32 @@
+export function test({ assert, input, js }) {
+ const expectedBar = input.locate('baritone:');
+ const expectedBaz = input.locate('baz:');
+
+ let start = js.locate('bar:');
+
+ const actualbar = js.mapConsumer.originalPositionFor({
+ line: start.line + 1,
+ column: start.column
+ });
+
+ assert.deepEqual(actualbar, {
+ source: 'input.svelte',
+ name: null,
+ line: expectedBar.line + 1,
+ column: expectedBar.column
+ }, "couldn't find bar: in source");
+
+ start = js.locate('baz:');
+
+ const actualbaz = js.mapConsumer.originalPositionFor({
+ line: start.line + 1,
+ column: start.column
+ });
+
+ assert.deepEqual(actualbaz, {
+ source: 'input.svelte',
+ name: null,
+ line: expectedBaz.line + 1,
+ column: expectedBaz.column
+ }, "couldn't find baz: in source");
+}
diff --git a/test/sourcemaps/samples/preprocessed-styles/_config.js b/test/sourcemaps/samples/preprocessed-styles/_config.js
new file mode 100644
index 000000000000..4e199e6d70c4
--- /dev/null
+++ b/test/sourcemaps/samples/preprocessed-styles/_config.js
@@ -0,0 +1,19 @@
+import MagicString from 'magic-string';
+
+export default {
+ preprocess: {
+ style: ({ content, filename }) => {
+ const src = new MagicString(content);
+ const idx = content.indexOf("baritone");
+ src.overwrite(idx, idx+"baritone".length, "bar");
+ return {
+ code: src.toString(),
+ map: src.generateMap({
+ source: filename,
+ hires: true,
+ includeContent: false
+ })
+ };
+ }
+ }
+};
diff --git a/test/sourcemaps/samples/preprocessed-styles/input.svelte b/test/sourcemaps/samples/preprocessed-styles/input.svelte
new file mode 100644
index 000000000000..0d942390f4b8
--- /dev/null
+++ b/test/sourcemaps/samples/preprocessed-styles/input.svelte
@@ -0,0 +1,12 @@
+Testing Styles
+Testing Styles 2
+
+
diff --git a/test/sourcemaps/samples/preprocessed-styles/test.js b/test/sourcemaps/samples/preprocessed-styles/test.js
new file mode 100644
index 000000000000..5b28a1251481
--- /dev/null
+++ b/test/sourcemaps/samples/preprocessed-styles/test.js
@@ -0,0 +1,32 @@
+export function test({ assert, input, css }) {
+ const expectedBar = input.locate('--baritone');
+ const expectedBaz = input.locate('--baz');
+
+ let start = css.locate('--bar');
+
+ const actualbar = css.mapConsumer.originalPositionFor({
+ line: start.line + 1,
+ column: start.column
+ });
+
+ assert.deepEqual(actualbar, {
+ source: 'input.svelte',
+ name: null,
+ line: expectedBar.line + 1,
+ column: expectedBar.column
+ }, "couldn't find bar in source");
+
+ start = css.locate('--baz');
+
+ const actualbaz = css.mapConsumer.originalPositionFor({
+ line: start.line + 1,
+ column: start.column
+ });
+
+ assert.deepEqual(actualbaz, {
+ source: 'input.svelte',
+ name: null,
+ line: expectedBaz.line + 1,
+ column: expectedBaz.column
+ }, "couldn't find baz in source");
+}
diff --git a/test/sourcemaps/samples/script-after-comment/test.js b/test/sourcemaps/samples/script-after-comment/test.js
index b4739701122a..06ecc46929df 100644
--- a/test/sourcemaps/samples/script-after-comment/test.js
+++ b/test/sourcemaps/samples/script-after-comment/test.js
@@ -1,13 +1,13 @@
-export function test({ assert, smc, locateInSource, locateInGenerated }) {
- const expected = locateInSource( 'assertThisLine' );
- const start = locateInGenerated( 'assertThisLine' );
+export function test({ assert, input, js }) {
+ const expected = input.locate('assertThisLine');
+ const start = js.locate('assertThisLine');
- const actual = smc.originalPositionFor({
+ const actual = js.mapConsumer.originalPositionFor({
line: start.line + 1,
column: start.column
});
- assert.deepEqual( actual, {
+ assert.deepEqual(actual, {
source: 'input.svelte',
name: null,
line: expected.line + 1,
diff --git a/test/sourcemaps/samples/script/test.js b/test/sourcemaps/samples/script/test.js
index 73971ef48763..e6a91f51e1c1 100644
--- a/test/sourcemaps/samples/script/test.js
+++ b/test/sourcemaps/samples/script/test.js
@@ -1,8 +1,8 @@
-export function test({ assert, smc, locateInSource, locateInGenerated }) {
- const expected = locateInSource( '42' );
- const start = locateInGenerated( '42' );
+export function test({ assert, input, js }) {
+ const expected = input.locate('42');
+ const start = js.locate('42');
- const actual = smc.originalPositionFor({
+ const actual = js.mapConsumer.originalPositionFor({
line: start.line + 1,
column: start.column
});
diff --git a/test/sourcemaps/samples/sourcemap-names/_config.js b/test/sourcemaps/samples/sourcemap-names/_config.js
new file mode 100644
index 000000000000..35c7badb296a
--- /dev/null
+++ b/test/sourcemaps/samples/sourcemap-names/_config.js
@@ -0,0 +1,50 @@
+import MagicString from 'magic-string';
+
+function replace(search, replace, content, src, options = { storeName: true }) {
+ let idx = -1;
+ while ((idx = content.indexOf(search, idx + 1)) != -1) {
+ src.overwrite(idx, idx + search.length, replace, options);
+ }
+}
+
+function result(src, filename) {
+ return {
+ code: src.toString(),
+ map: src.generateDecodedMap({
+ source: filename,
+ hires: true,
+ includeContent: false
+ })
+ };
+}
+
+export default {
+ preprocess: [
+ {
+ markup: ({ content, filename }) => {
+ const src = new MagicString(content);
+ replace('baritone', 'bar', content, src);
+ replace('--bazitone', '--baz', content, src);
+ replace('old_name_1', 'temp_new_name_1', content, src);
+ replace('old_name_2', 'temp_new_name_2', content, src);
+ return result(src, filename);
+ }
+ },
+ {
+ markup: ({ content, filename }) => {
+ const src = new MagicString(content);
+ replace('temp_new_name_1', 'temp_temp_new_name_1', content, src);
+ replace('temp_new_name_2', 'temp_temp_new_name_2', content, src);
+ return result(src, filename);
+ }
+ },
+ {
+ markup: ({ content, filename }) => {
+ const src = new MagicString(content);
+ replace('temp_temp_new_name_1', 'new_name_1', content, src);
+ replace('temp_temp_new_name_2', 'new_name_2', content, src);
+ return result(src, filename);
+ }
+ }
+ ]
+};
diff --git a/test/sourcemaps/samples/sourcemap-names/input.svelte b/test/sourcemaps/samples/sourcemap-names/input.svelte
new file mode 100644
index 000000000000..b62715a85713
--- /dev/null
+++ b/test/sourcemaps/samples/sourcemap-names/input.svelte
@@ -0,0 +1,12 @@
+
+
+use-names
+{old_name_1.baritone}
+{old_name_2}
diff --git a/test/sourcemaps/samples/sourcemap-names/test.js b/test/sourcemaps/samples/sourcemap-names/test.js
new file mode 100644
index 000000000000..85f4b1afdba5
--- /dev/null
+++ b/test/sourcemaps/samples/sourcemap-names/test.js
@@ -0,0 +1,43 @@
+// needed for workaround, TODO remove
+import { getLocator } from 'locate-character';
+
+export function test({ assert, input, preprocessed, js, css }) {
+
+ assert.deepEqual(
+ preprocessed.map.names.sort(),
+ ['baritone', '--bazitone', 'old_name_1', 'old_name_2'].sort()
+ );
+
+ // TODO move fn test_name to test/sourcemaps/index.js and use in samples/*/test.js
+ function test_name(old_name, new_name, where) {
+
+ let loc = { character: -1 };
+ while (loc = where.locate(new_name, loc.character + 1)) {
+ const actualMapping = where.mapConsumer.originalPositionFor({
+ line: loc.line + 1, column: loc.column
+ });
+ if (actualMapping.line === null) {
+ // location is not mapped - ignore
+ continue;
+ }
+ assert.equal(actualMapping.name, old_name);
+ }
+ if (loc === undefined) {
+ // workaround for bug in locate-character, TODO remove
+ // https://github.com/Rich-Harris/locate-character/pull/5
+ where.locate = getLocator(where.code);
+ }
+ }
+
+ test_name('baritone', 'bar', js);
+ test_name('baritone', 'bar', preprocessed);
+
+ test_name('--bazitone', '--baz', css);
+ test_name('--bazitone', '--baz', preprocessed);
+
+ test_name('old_name_1', 'new_name_1', js);
+ test_name('old_name_1', 'new_name_1', preprocessed);
+
+ test_name('old_name_2', 'new_name_2', js);
+ test_name('old_name_2', 'new_name_2', preprocessed);
+}
diff --git a/test/sourcemaps/samples/sourcemap-sources/_config.js b/test/sourcemaps/samples/sourcemap-sources/_config.js
new file mode 100644
index 000000000000..999fb20dfeff
--- /dev/null
+++ b/test/sourcemaps/samples/sourcemap-sources/_config.js
@@ -0,0 +1,55 @@
+import MagicString, { Bundle } from 'magic-string';
+
+function add(bundle, filename, source) {
+ bundle.addSource({
+ filename,
+ content: new MagicString(source),
+ separator: '\n'
+ //separator: '' // ERROR. probably a bug in magic-string
+ });
+}
+
+function result(bundle, filename) {
+ return {
+ code: bundle.toString(),
+ map: bundle.generateMap({
+ file: filename,
+ includeContent: false,
+ hires: true // required for remapping
+ })
+ };
+}
+
+export default {
+ js_map_sources: [
+ 'input.svelte',
+ 'foo.js',
+ 'bar.js',
+ 'foo2.js',
+ 'bar2.js'
+ ],
+ preprocess: [
+ {
+ script: ({ content, filename }) => {
+ const bundle = new Bundle();
+
+ add(bundle, filename, content);
+ add(bundle, 'foo.js', 'var answer = 42; // foo.js\n');
+ add(bundle, 'bar.js', 'console.log(answer); // bar.js\n');
+
+ return result(bundle, filename);
+ }
+ },
+ {
+ script: ({ content, filename }) => {
+ const bundle = new Bundle();
+
+ add(bundle, filename, content);
+ add(bundle, 'foo2.js', 'var answer2 = 84; // foo2.js\n');
+ add(bundle, 'bar2.js', 'console.log(answer2); // bar2.js\n');
+
+ return result(bundle, filename);
+ }
+ }
+ ]
+};
diff --git a/test/sourcemaps/samples/sourcemap-sources/input.svelte b/test/sourcemaps/samples/sourcemap-sources/input.svelte
new file mode 100644
index 000000000000..33c8a9d9a66b
--- /dev/null
+++ b/test/sourcemaps/samples/sourcemap-sources/input.svelte
@@ -0,0 +1,4 @@
+
+sourcemap-sources
diff --git a/test/sourcemaps/samples/sourcemap-sources/test.js b/test/sourcemaps/samples/sourcemap-sources/test.js
new file mode 100644
index 000000000000..78a4c80a1748
--- /dev/null
+++ b/test/sourcemaps/samples/sourcemap-sources/test.js
@@ -0,0 +1,29 @@
+export function test({ assert, preprocessed, js }) {
+
+ assert.equal(preprocessed.error, undefined);
+
+ // sourcemap stores location only for 'answer = 42;'
+ // not for 'var answer = 42;'
+ [
+ [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, column]) => {
+
+ assert.deepEqual(
+ where.mapConsumer.originalPositionFor(
+ where.locate_1(content)
+ ),
+ {
+ source: sourcefile,
+ name: null,
+ line: 1,
+ column
+ },
+ `failed to locate "${content}" from "${sourcefile}"`
+ );
+
+ });
+}
diff --git a/test/sourcemaps/samples/static-no-script/input.svelte b/test/sourcemaps/samples/static-no-script/input.svelte
index 1e2d59797188..a6a67113e10f 100644
--- a/test/sourcemaps/samples/static-no-script/input.svelte
+++ b/test/sourcemaps/samples/static-no-script/input.svelte
@@ -1 +1 @@
-no moving parts
\ No newline at end of file
+no moving parts
diff --git a/test/sourcemaps/samples/static-no-script/test.js b/test/sourcemaps/samples/static-no-script/test.js
index a8f4d89ab822..c683c94d6b31 100644
--- a/test/sourcemaps/samples/static-no-script/test.js
+++ b/test/sourcemaps/samples/static-no-script/test.js
@@ -1,9 +1,9 @@
-const fs = require( 'fs' );
-const path = require( 'path' );
+const fs = require('fs');
+const path = require('path');
-export function test({ assert, map }) {
- assert.deepEqual( map.sources, [ 'input.svelte' ]);
- assert.deepEqual( map.sourcesContent, [
- fs.readFileSync( path.join( __dirname, 'input.svelte' ), 'utf-8' )
+export function test({ assert, js }) {
+ assert.deepEqual(js.map.sources, ['input.svelte']);
+ assert.deepEqual(js.map.sourcesContent, [
+ fs.readFileSync(path.join(__dirname, 'input.svelte'), 'utf-8')
]);
}
diff --git a/test/sourcemaps/samples/two-scripts/test.js b/test/sourcemaps/samples/two-scripts/test.js
index b4739701122a..70901af8c9d2 100644
--- a/test/sourcemaps/samples/two-scripts/test.js
+++ b/test/sourcemaps/samples/two-scripts/test.js
@@ -1,8 +1,8 @@
-export function test({ assert, smc, locateInSource, locateInGenerated }) {
- const expected = locateInSource( 'assertThisLine' );
- const start = locateInGenerated( 'assertThisLine' );
+export function test({ assert, input, js }) {
+ const expected = input.locate( 'assertThisLine' );
+ const start = js.locate( 'assertThisLine' );
- const actual = smc.originalPositionFor({
+ const actual = js.mapConsumer.originalPositionFor({
line: start.line + 1,
column: start.column
});
diff --git a/test/sourcemaps/samples/warn-on-encoded-mappings/_config.js b/test/sourcemaps/samples/warn-on-encoded-mappings/_config.js
new file mode 100644
index 000000000000..d33573e27d72
--- /dev/null
+++ b/test/sourcemaps/samples/warn-on-encoded-mappings/_config.js
@@ -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);
+ } }
+ ]
+
+};
diff --git a/test/sourcemaps/samples/warn-on-encoded-mappings/input.svelte b/test/sourcemaps/samples/warn-on-encoded-mappings/input.svelte
new file mode 100644
index 000000000000..ac8f9b30ab75
--- /dev/null
+++ b/test/sourcemaps/samples/warn-on-encoded-mappings/input.svelte
@@ -0,0 +1,2 @@
+replace_me
+replace_me
diff --git a/test/sourcemaps/samples/warn-on-encoded-mappings/test.js b/test/sourcemaps/samples/warn-on-encoded-mappings/test.js
new file mode 100644
index 000000000000..9e25f21e55c7
--- /dev/null
+++ b/test/sourcemaps/samples/warn-on-encoded-mappings/test.js
@@ -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). [....]
+
+}