mirror of https://github.com/sveltejs/svelte
parent
b5b02f8561
commit
e223c35f1a
@ -0,0 +1,189 @@
|
||||
import { encode } from "sourcemap-codec";
|
||||
|
||||
type MappingSegment = [ number ] | [ number, number, number, number ] | [ number, number, number, number, number ]
|
||||
|
||||
type SourceMappings = {
|
||||
sources: string[];
|
||||
names: string[];
|
||||
mappings: MappingSegment[][];
|
||||
}
|
||||
|
||||
type SourceLocation = {
|
||||
line: number;
|
||||
column: number;
|
||||
}
|
||||
|
||||
function get_end_location(s: string): SourceLocation {
|
||||
const parts = s.split('\n');
|
||||
return {
|
||||
line: parts.length - 1,
|
||||
column: parts[parts.length - 1].length - 1
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export function offset_source_location(offset: SourceLocation, map: SourceMappings): SourceMappings {
|
||||
|
||||
const new_mappings = map.mappings.map(line => line.map(seg => {
|
||||
if (seg.length < 3) return seg;
|
||||
const new_seg = seg.slice() as MappingSegment;
|
||||
new_seg[2] = new_seg[2] + offset.line;
|
||||
return new_seg;
|
||||
}));
|
||||
|
||||
// first line has column altered
|
||||
if (new_mappings.length > 0) {
|
||||
new_mappings[0] = new_mappings[0].map(seg => {
|
||||
if (seg.length < 4) return seg;
|
||||
const newSeg = seg.slice() as MappingSegment;
|
||||
newSeg[3] = newSeg[3] + offset.column;
|
||||
return newSeg;
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
sources: map.sources,
|
||||
mappings: new_mappings
|
||||
} as SourceMappings;
|
||||
}
|
||||
|
||||
|
||||
|
||||
function merge_tables<T>( original: T[], extended: T[]): { table: T[]; new_idx: number[] } {
|
||||
const table = original.slice();
|
||||
const new_idx = [];
|
||||
for (let j = 0; j < original.length; j++) {
|
||||
const current = extended[j];
|
||||
const existing = table.indexOf(current);
|
||||
if (existing < 0) {
|
||||
table.push(current);
|
||||
new_idx[j] = table.length - 1;
|
||||
} else {
|
||||
new_idx[j] = existing;
|
||||
}
|
||||
}
|
||||
return { table, new_idx };
|
||||
}
|
||||
|
||||
|
||||
|
||||
export class GeneratedStringWithMap {
|
||||
readonly generated: string;
|
||||
readonly map: SourceMappings;
|
||||
|
||||
constructor(generated: string , map: SourceMappings) {
|
||||
this.generated = generated;
|
||||
this.map = map;
|
||||
}
|
||||
|
||||
as_sourcemap() {
|
||||
return {
|
||||
version: 3,
|
||||
sources: this.map.sources,
|
||||
names: [],
|
||||
mappings: encode(this.map.mappings as any)
|
||||
};
|
||||
}
|
||||
|
||||
concat(other: GeneratedStringWithMap): GeneratedStringWithMap {
|
||||
// if one is empty, return the other
|
||||
if (this.generated.length == 0) return other;
|
||||
if (other.generated.length == 0) return this;
|
||||
|
||||
//combine sources
|
||||
const { table: new_sources, new_idx: other_source_idx } = merge_tables(this.map.sources, other.map.sources);
|
||||
const { table: new_names, new_idx: other_name_idx } = merge_tables(this.map.names, other.map.names);
|
||||
|
||||
//update source and name references in segments
|
||||
const other_mappings = other.map.mappings.map(line => line.map(seg => {
|
||||
//to reduce allocations, we only return a new segment if a value has changed
|
||||
if (
|
||||
(seg.length > 1 && other_source_idx[seg[1]] != seg[1]) // has source idx that has been updated
|
||||
|| (seg.length == 5 && other_name_idx[seg[4]] != seg[4])) // has name idx that has been updated
|
||||
{
|
||||
const new_seg = seg.slice() as MappingSegment;
|
||||
new_seg[1] = other_source_idx[seg[1]];
|
||||
if (seg.length == 5) {
|
||||
new_seg[4] = other_name_idx[seg[4]];
|
||||
}
|
||||
return new_seg;
|
||||
} else {
|
||||
return seg;
|
||||
}
|
||||
}));
|
||||
|
||||
//combine the mappings
|
||||
let new_mappings = this.map.mappings.slice();
|
||||
|
||||
//shift the first line of the second mapping by the number of columns in the last line of the first
|
||||
const end = get_end_location(this.generated);
|
||||
const col_offset = end.column + 1;
|
||||
const first_line = other_mappings.length == 0 ? [] : other_mappings[0].map(seg => {
|
||||
const new_seg = seg.slice() as MappingSegment;
|
||||
new_seg[0] = seg[0] + col_offset;
|
||||
return new_seg;
|
||||
});
|
||||
new_mappings[new_mappings.length - 1] = new_mappings[new_mappings.length - 1].concat(first_line);
|
||||
|
||||
//the rest don't need modification and can just be appended
|
||||
new_mappings = new_mappings.concat(other_mappings.slice(1) as MappingSegment[][]);
|
||||
|
||||
return new GeneratedStringWithMap(this.generated + other.generated, {
|
||||
sources: new_sources,
|
||||
names: new_names,
|
||||
mappings: new_mappings
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
static from_generated(generated: string, map?: SourceMappings): GeneratedStringWithMap {
|
||||
if (map) return new GeneratedStringWithMap(generated, map);
|
||||
|
||||
const replacement_map: SourceMappings = {
|
||||
names: [],
|
||||
sources: [],
|
||||
mappings: []
|
||||
};
|
||||
|
||||
if (generated.length == 0) return new GeneratedStringWithMap(generated, replacement_map);
|
||||
|
||||
// we generate a mapping where the source was overwritten by the generated
|
||||
const end = get_end_location(generated);
|
||||
for (let i = 0; i <= end.line; i++) {
|
||||
replacement_map.mappings.push([]); // unmapped line
|
||||
}
|
||||
|
||||
return new GeneratedStringWithMap(generated, replacement_map);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static from_source(source_file: string, source: string, offset_in_source?: SourceLocation): GeneratedStringWithMap {
|
||||
const offset = offset_in_source || { line: 0, column: 0 };
|
||||
const map: SourceMappings = {
|
||||
names: [],
|
||||
sources: [ source_file ],
|
||||
mappings: []
|
||||
};
|
||||
|
||||
if (source.length == 0) return new GeneratedStringWithMap(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.
|
||||
const lines = source.split('\n');
|
||||
let pos = 0;
|
||||
const identity_map = lines.map((line, line_idx) => {
|
||||
const segs = line.split(/([^\d\w\s]|\s+)/g).filter(x => x !== "").map(s => {
|
||||
const seg: MappingSegment = [pos, 0, offset.line + line_idx, pos + (line_idx == 0 ? offset.column : 0)];
|
||||
pos = pos + s.length;
|
||||
return seg;
|
||||
});
|
||||
pos = 0;
|
||||
return segs;
|
||||
});
|
||||
|
||||
map.mappings = identity_map;
|
||||
|
||||
return new GeneratedStringWithMap(source, map);
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
import MagicString from 'magic-string';
|
||||
|
||||
export const preprocessors = [{
|
||||
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.generateMap({
|
||||
source: filename,
|
||||
includeContent: false
|
||||
})
|
||||
};
|
||||
}
|
||||
}];
|
@ -0,0 +1,5 @@
|
||||
<script>
|
||||
export let foo;
|
||||
</script>
|
||||
|
||||
{foo.baritone.baz}
|
@ -0,0 +1,32 @@
|
||||
export function test({ assert, smc, locateInSource, locateInGenerated }) {
|
||||
const expectedBar = locateInSource('baritone.baz');
|
||||
const expectedBaz = locateInSource('.baz');
|
||||
|
||||
let start = locateInGenerated('bar.baz');
|
||||
|
||||
const actualbar = smc.originalPositionFor({
|
||||
line: start.line + 1,
|
||||
column: start.column
|
||||
});
|
||||
|
||||
assert.deepEqual(actualbar, {
|
||||
source: 'input.svelte',
|
||||
name: null,
|
||||
line: expectedBar.line + 1,
|
||||
column: expectedBar.column
|
||||
});
|
||||
|
||||
start = locateInGenerated('.baz');
|
||||
|
||||
const actualbaz = smc.originalPositionFor({
|
||||
line: start.line + 1,
|
||||
column: start.column
|
||||
});
|
||||
|
||||
assert.deepEqual(actualbaz, {
|
||||
source: 'input.svelte',
|
||||
name: null,
|
||||
line: expectedBaz.line + 1,
|
||||
column: expectedBaz.column
|
||||
});
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
import MagicString from 'magic-string';
|
||||
|
||||
export const preprocessors = [{
|
||||
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.generateMap({
|
||||
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.generateMap({
|
||||
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.generateMap({
|
||||
source: filename,
|
||||
hires: true,
|
||||
includeContent: false
|
||||
})
|
||||
};
|
||||
}
|
||||
}
|
||||
];
|
@ -0,0 +1,9 @@
|
||||
<script>
|
||||
export let foo = { baritone: 5 }
|
||||
</script>
|
||||
<style>
|
||||
h1 {
|
||||
background-color: var(--bazitone);
|
||||
}
|
||||
</style>
|
||||
<h1>multiple {foo}</h1>
|
@ -0,0 +1,32 @@
|
||||
export function test({ assert, smc, smcCss, locateInSource, locateInGenerated, locateInGeneratedCss }) {
|
||||
const expectedBar = locateInSource('baritone');
|
||||
const expectedBaz = locateInSource('--bazitone');
|
||||
|
||||
let start = locateInGenerated('bar');
|
||||
|
||||
const actualbar = smc.originalPositionFor({
|
||||
line: start.line + 1,
|
||||
column: start.column
|
||||
});
|
||||
|
||||
assert.deepEqual(actualbar, {
|
||||
source: 'input.svelte',
|
||||
name: null,
|
||||
line: expectedBar.line + 1,
|
||||
column: expectedBar.column
|
||||
});
|
||||
|
||||
start = locateInGeneratedCss('--baz');
|
||||
|
||||
const actualbaz = smcCss.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,\n gen:${JSON.stringify(start)}\n actual:${JSON.stringify(actualbaz)}\n expected:${JSON.stringify(expectedBaz)}`);
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
import MagicString from 'magic-string';
|
||||
|
||||
export const preprocessors = [{
|
||||
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
|
||||
})
|
||||
};
|
||||
}
|
||||
}];
|
@ -0,0 +1,9 @@
|
||||
<style>
|
||||
h1 {
|
||||
color: red;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
export let foo = { baritone: { baz: 5 } }
|
||||
</script>
|
||||
<h1>{foo.bar.baz}</h1>
|
@ -0,0 +1,32 @@
|
||||
export function test({ assert, smc, locateInSource, locateInGenerated }) {
|
||||
const expectedBar = locateInSource('baritone:');
|
||||
const expectedBaz = locateInSource('baz:');
|
||||
|
||||
let start = locateInGenerated('bar:');
|
||||
|
||||
const actualbar = smc.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 = locateInGenerated('baz:');
|
||||
|
||||
const actualbaz = smc.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` );
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
import MagicString from 'magic-string';
|
||||
|
||||
export const preprocessors = [{
|
||||
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
|
||||
})
|
||||
};
|
||||
}
|
||||
}];
|
@ -0,0 +1,13 @@
|
||||
<h1>Testing Styles</h1>
|
||||
<h2>Testing Styles 2</h2>
|
||||
<script>export const b = 2;</script>
|
||||
<style>
|
||||
h1 {
|
||||
--baritone: red;
|
||||
}
|
||||
|
||||
h2 {
|
||||
--baz: blue;
|
||||
}
|
||||
</style>
|
||||
|
@ -0,0 +1,32 @@
|
||||
export function test({ assert, smcCss, locateInSource, locateInGeneratedCss }) {
|
||||
const expectedBar = locateInSource('--baritone');
|
||||
const expectedBaz = locateInSource('--baz');
|
||||
|
||||
let start = locateInGeneratedCss('--bar');
|
||||
|
||||
const actualbar = smcCss.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 = locateInGeneratedCss('--baz');
|
||||
|
||||
const actualbaz = smcCss.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` );
|
||||
}
|
Loading…
Reference in new issue