mirror of https://github.com/sveltejs/svelte
parent
8bb0c80c2c
commit
6c0a867958
@ -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