Skip to content

Commit 4d2b098

Browse files
authored
(chore) Svelte2tsx SourceMapping tests rewrite (#864)
Changed - Relocated samples to samples/{name}. Relocated svelte input to input.svelte, relocated tests to test.jsx - Added source-map-codec to devDependencies to decode mappings Added - Added auto-generating file mappings.jsx, it contains the generated output with a comment under each line describing each character's mapping and how they map back to the origin. The intent of that file is to highlight changes on diffchecks - Added auto-generating untracked file test.edit.jsx, it mirrors the ranges tested in test.jsx in the form of the generated output with range positions surrounded by triple brackets. Editing this file and running tests will regenerate new tests based on those ranges - Added auto-generating untracked file output.tsx, It contains the generated output with its inlined sourcemap. That file can be directly uploaded to online sourcemapping tools, e.g. https://evanw.github.io/source-map-visualization/
1 parent 3afa685 commit 4d2b098

29 files changed

+1823
-399
lines changed

.prettierignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
packages/svelte2tsx/*.d.ts
22
packages/svelte2tsx/test/*/samples/*/*sx
33
packages/svelte2tsx/test/*/samples/*/*.svelte
4-
packages/svelte2tsx/test/sourcemaps/*.html
4+
packages/svelte2tsx/test/sourcemaps/samples/*
55
packages/language-server/test/**/*.svelte
66
packages/language-server/test/**/testfiles/**/*.ts
77
packages/svelte-vscode/syntaxes/*.yaml

packages/svelte2tsx/.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,6 @@ node_modules
44
/index.js.map
55
/index.mjs
66
test/typecheck/samples/**/input.svelte.tsx
7-
repl/output
7+
test/sourcemaps/samples/*/output.tsx
8+
test/sourcemaps/samples/*/test.edit.jsx
9+
repl/output

packages/svelte2tsx/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
"rollup-plugin-delete": "^2.0.0",
3434
"source-map": "^0.6.1",
3535
"source-map-support": "^0.5.16",
36+
"sourcemap-codec": "^1.4.4",
3637
"svelte": "~3.35.0",
3738
"tiny-glob": "^0.2.6",
3839
"tslib": "^1.10.0",

packages/svelte2tsx/test/helpers.ts

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ import { htmlx2jsx, svelte2tsx } from './build';
55
import path from 'path';
66

77
let update_count = 0;
8+
let all_tests_skipped = false;
9+
810
function can_auto_update() {
9-
if (!process.argv.includes('--auto')) {
11+
if (!process.argv.includes('--auto') && !all_tests_skipped) {
1012
if (update_count++ === 0) {
1113
process.on('exit', () => {
12-
const command = color.yellow('npm run test -- --auto');
14+
const command = color.yellow('yarn run test --auto');
1315
console.log(` Run ${command} to update ${update_count} files\n`);
1416
});
1517
}
@@ -37,7 +39,7 @@ function writeFileSync(path: string, content: string) {
3739
return fs.writeFileSync(path, normalize(content));
3840
}
3941

40-
function wildcard(str: string, target: string) {
42+
function is_same_file(str: string, target: string) {
4143
if (str[0] === '*') return target.endsWith(str.slice(1));
4244
return str === target;
4345
}
@@ -62,13 +64,13 @@ export class Sample {
6264

6365
loop: for (const fileName of this.folder) {
6466
for (const name of unchecked) {
65-
if (wildcard(name, fileName)) {
67+
if (is_same_file(name, fileName)) {
6668
unchecked.delete(name);
6769
continue loop;
6870
}
6971
}
7072
for (const name of allowed) {
71-
if (wildcard(name, fileName)) {
73+
if (is_same_file(name, fileName)) {
7274
continue loop;
7375
}
7476
}
@@ -138,8 +140,12 @@ export class Sample {
138140
return this.folder.length === fileNames.length && fileNames.every((f) => this.has(f));
139141
}
140142

141-
wildcard(fileName: string) {
142-
return this.folder.find((f) => wildcard(fileName, f));
143+
/**
144+
* Returns first found file that matches the fileName,
145+
* or ends with it in case of a wildcard search.
146+
*/
147+
find_file(fileName: string) {
148+
return this.folder.find((f) => is_same_file(fileName, f));
143149
}
144150

145151
at(file: string) {
@@ -158,6 +164,7 @@ export class Sample {
158164
if (process.env.CI) {
159165
throw new Error(`Tried to generate ${this.name} dependencies`);
160166
}
167+
all_tests_skipped = true;
161168
it.only(`${this.name} dependencies`, () => fn(this.generate.bind(this)));
162169
}
163170

@@ -203,7 +210,7 @@ const enum TestError {
203210

204211
export function test_samples(dir: string, transform: TransformSampleFn, jsx: 'jsx' | 'tsx') {
205212
for (const sample of each_sample(dir)) {
206-
const svelteFile = sample.wildcard('*.svelte');
213+
const svelteFile = sample.find_file('*.svelte');
207214
const config = {
208215
filename: svelteFile,
209216
sampleName: sample.name,
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import { get_extra_indent, Segment } from './helpers';
2+
import { Line } from './parser';
3+
4+
type cmap = string | Segment;
5+
type CommentOptions = { indent?: number };
6+
type comment = [cmap, string] | readonly [cmap, string];
7+
type Section = HorizontalRule | Comment | Line | string;
8+
9+
class Comment {
10+
readonly content: [string, string][];
11+
12+
constructor(gen: Iterable<comment>, readonly options: CommentOptions = {}) {
13+
this.content = Array.from(gen, ([cmap, message]): [string, string] => [
14+
typeof cmap === 'string' ? cmap : ' '.repeat(cmap.start) + cmap.text,
15+
message
16+
]);
17+
}
18+
19+
render(indent: number, col0_width: number, width: number) {
20+
col0_width = Math.max(col0_width, ...this.content.map(([map]) => map.length));
21+
if ('indent' in this.options) indent = this.options.indent;
22+
return this.content
23+
.map(([map, comment]) =>
24+
(' '.repeat(indent * 4) + map.padEnd(col0_width) + ' ' + comment).padEnd(width)
25+
)
26+
.join('\n');
27+
}
28+
}
29+
30+
class HorizontalRule {
31+
constructor(readonly content?: string, readonly _fill: string = '-') {}
32+
33+
fill(width: number) {
34+
return this.content
35+
? this.content
36+
.padStart(Math.floor((width + this.content.length) / 2), this._fill)
37+
.padEnd(width, this._fill)
38+
: this._fill.repeat(width);
39+
}
40+
}
41+
42+
/**
43+
* Creates a rule that puts the passed in context in the middle and fills up remaining
44+
* width around it with the given fill.
45+
*/
46+
function rule(content?: string, fill?: string): HorizontalRule {
47+
return new HorizontalRule(content, fill);
48+
}
49+
50+
/**
51+
* Prints given line(s) in a multi-line comment block. (succeeding comments are joined together)
52+
*/
53+
function comment(lines: comment | Iterable<comment>, opts?: CommentOptions) {
54+
return new Comment(
55+
lines instanceof Array && lines.length === 2 && !(lines[0] instanceof Array)
56+
? [lines]
57+
: (lines as Iterable<comment>),
58+
opts
59+
);
60+
}
61+
62+
function is_inside_comment(section: Section) {
63+
return section instanceof HorizontalRule || section instanceof Comment;
64+
}
65+
66+
export type ComposeHelper = { rule: typeof rule; comment: typeof comment };
67+
68+
export function compose_file(
69+
fn: (composer: ComposeHelper) => Iterable<Section>,
70+
opts: { width?: number; indent?: number; match_first_column_width?: boolean } = {}
71+
) {
72+
const sections = [...fn({ rule, comment })];
73+
const width = opts.width ?? 150;
74+
const min_col0_width = opts.match_first_column_width
75+
? Math.max(
76+
...sections.map((section) =>
77+
section instanceof Comment
78+
? Math.max(...section.content.map((line) => line[0].length))
79+
: 0
80+
)
81+
)
82+
: 0;
83+
84+
const content: string[] = [];
85+
if (sections[0] instanceof HorizontalRule || sections[0] instanceof Comment) {
86+
sections.unshift('');
87+
}
88+
89+
for (let i = 0; i < sections.length; i++) {
90+
const { [i]: current, [i + 1]: next } = sections;
91+
let str = '';
92+
if (current instanceof HorizontalRule) {
93+
if (next instanceof HorizontalRule) continue;
94+
str = current.fill(width);
95+
if (!is_inside_comment(next)) {
96+
if (content[content.length - 1].includes('//')) {
97+
str = '//' + current.fill(width - 2);
98+
} else {
99+
str += comment_end;
100+
}
101+
}
102+
} else if (current instanceof Comment) {
103+
str = current.render(opts.indent ?? 0, min_col0_width, width);
104+
if (!(next === undefined || is_inside_comment(next))) str += comment_end;
105+
} else {
106+
str = ('' + current).trimRight().replace(/\t/g, ' ');
107+
if (is_inside_comment(next) && !str.includes('//')) {
108+
str = str.padEnd(width - get_extra_indent(str)) + comment_start;
109+
}
110+
}
111+
content.push(str);
112+
}
113+
114+
return content.join('\n');
115+
}
116+
117+
const comment_start = '{/**';
118+
const comment_end = ' */}';

packages/svelte2tsx/test/sourcemaps/event-binding.html

Lines changed: 0 additions & 15 deletions
This file was deleted.
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import { GeneratedLine, Line } from './parser';
2+
3+
export type CharMap4 = [
4+
generated_charIndex: number,
5+
original_fileIndex: number,
6+
original_lineIndex: number,
7+
original_charIndex: number
8+
];
9+
10+
export type LineMap = CharMap4[];
11+
export type Mappings = LineMap[];
12+
export type Segment = { start: number; text: string };
13+
export type Range = { start: Position; end: Position };
14+
export type MappedPosition = { generated: GeneratedPosition; original: Position };
15+
export type MappedRange = { start: MappedPosition; end: MappedPosition };
16+
export const MappedKeys: (keyof MappedPosition)[] = ['generated', 'original'];
17+
18+
export interface Position {
19+
line: Line;
20+
character: number;
21+
}
22+
export interface GeneratedPosition extends Position {
23+
line: GeneratedLine;
24+
}
25+
26+
export function print_string(str: string) {
27+
return ('' + str)
28+
.replace(/ /g, '•')
29+
.replace(/[\r\n]/g, '↲')
30+
.replace(/\t/g, '╚');
31+
}
32+
33+
function edit_string(str: string, { start, text }: Segment, insert: boolean) {
34+
if (str.length === start) return str + text;
35+
else if (str.length < start) return str.padEnd(start) + text;
36+
else return str.slice(0, start) + text + str.slice(start + (insert ? 0 : text.length));
37+
}
38+
39+
/**
40+
* Reduces segments into one big string
41+
*/
42+
export function reduce_segments<T>(
43+
gen: Iterable<T>,
44+
fn: (value: T, index: number) => Segment | void,
45+
initial = ''
46+
) {
47+
let str = initial;
48+
let i = 0;
49+
for (const value of gen) {
50+
const segment = fn(value, i++);
51+
if (segment) str = edit_string(str, segment, false);
52+
}
53+
return str;
54+
}
55+
56+
/**
57+
* Inserts each segment in the given text
58+
*/
59+
export function insert_segments(text: string, segments: Iterable<Segment>) {
60+
let str = '';
61+
let prev_start: number = undefined;
62+
// sort descending
63+
for (const segment of [...segments].sort((a, b) => b.start - a.start)) {
64+
str = segment.text + text.slice(segment.start, prev_start) + str;
65+
prev_start = segment.start;
66+
}
67+
return text.slice(0, prev_start) + str;
68+
}
69+
70+
export function fromLineCharToOffset({ line, character }: { line: Line; character: number }) {
71+
return line.start + character;
72+
}
73+
74+
/**
75+
* Returns a text span starting with head, ending with tail, and repeating body in the middle.
76+
*/
77+
export function span(length: number, [head, body, tail]: string = '#==') {
78+
if (length <= 1) return length < 1 ? '' : body === tail ? head : tail;
79+
return head + body.repeat(length - 2) + tail;
80+
}
81+
82+
class Underline {
83+
constructor(readonly start: number, readonly text: string) {}
84+
85+
toString() {
86+
return ' '.repeat(this.start) + this.text;
87+
}
88+
}
89+
90+
export function underline(start: number, end: number, style?: string) {
91+
return new Underline(start, span(end - start + 1, style));
92+
}
93+
94+
export function* each_subset(lineMap: LineMap) {
95+
let char = lineMap[0];
96+
for (let i = 1; i < lineMap.length; i++) {
97+
if (char[2] !== lineMap[i][2]) {
98+
yield { line: char[2], start: char[0], end: (char = lineMap[i])[0] };
99+
}
100+
}
101+
yield { line: char[2], start: char[0], end: undefined };
102+
}
103+
104+
export function* each_exec(str: string, re: RegExp) {
105+
let arr: RegExpExecArray;
106+
while ((arr = re.exec(str))) yield { match: arr[0], index: arr.index };
107+
}
108+
109+
/**
110+
* Returns offset of character at index after accounting for extra space taken by tabs
111+
*/
112+
export function tab_aware_index(str: string, index: number) {
113+
return index + get_extra_indent(str.slice(0, index + 1));
114+
}
115+
116+
/**
117+
* Returns all extra space taken by tabs in given string
118+
*/
119+
export function get_extra_indent(str: string) {
120+
return (str.match(/\t/g)?.length ?? 0) * 3;
121+
}
122+
123+
export function range_for<K extends keyof MappedPosition>(key: K, range: MappedRange) {
124+
return { start: range.start[key], end: range.end[key] };
125+
}
126+
127+
export function hash(str: string): string {
128+
str = str.replace(/\r/g, '');
129+
let hash = 5381;
130+
let i = str.length;
131+
while (i--) hash = ((hash << 5) - hash) ^ str.charCodeAt(i);
132+
return (hash >>> 0).toString(36);
133+
}

0 commit comments

Comments
 (0)