Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 16 additions & 6 deletions apps/svelte.dev/src/lib/tutorial/adapters/rollup/index.svelte.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import type { Adapter, Warning } from '$lib/tutorial';
import type { File, Item } from 'editor';
import Bundler from '@sveltejs/repl/bundler';
// @ts-ignore package exports don't have types
import * as yootils from 'yootils';
import type { Adapter } from '$lib/tutorial';
import type { File, Item } from 'editor';
import type { CompileError, Warning } from 'svelte/compiler';

/** Rollup bundler singleton */
let bundler: Bundler;

export const state = new (class RollupState {
progress = $state.raw({ value: 0, text: 'initialising' });
bundle = $state.raw<any>(null);
errors = $state.raw<Record<string, CompileError | null>>();
warnings = $state.raw<Record<string, Warning[]>>({});
})();

Expand Down Expand Up @@ -51,15 +53,23 @@ export async function create(): Promise<Adapter> {
type: f.name.split('.').pop() ?? 'svelte'
}))
);

state.bundle = result;

const _warnings: Record<string, any> = {};
// TODO this approach is insufficient — we need to get diagnostics for
// individual files, not just the bundle as a whole
state.errors = {};
state.warnings = {};

if (result.error) {
const file = '/src/lib/' + result.error.filename;
state.errors[file] = result.error;
}

for (const warning of result?.warnings ?? []) {
const file = '/src/lib/' + warning.filename;
_warnings[file] = _warnings[file] || [];
_warnings[file].push(warning);
(state.warnings[file] ??= []).push(warning);
}
state.warnings = _warnings;
}

const q = yootils.queue(1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import * as yootils from 'yootils';
import { get_depth } from '../../../utils/path.js';
import { escape_html } from '../../../utils/escape.js';
import { ready } from '../common/index.js';
import type { Adapter, Warning } from '$lib/tutorial';
import type { Adapter } from '$lib/tutorial';
import type { Item, File } from 'editor';
import type { CompileError, Warning } from 'svelte/compiler';

const converter = new AnsiToHtml({
fg: 'var(--sk-text-3)'
Expand All @@ -21,6 +22,7 @@ export const state = new (class WCState {
base = $state.raw<string | null>(null);
error = $state.raw<Error | null>(null);
logs = $state.raw<string[]>([]);
errors = $state.raw<Record<string, CompileError | null>>({});
warnings = $state.raw<Record<string, Warning[]>>({});
})();

Expand All @@ -47,7 +49,7 @@ export async function create(): Promise<Adapter> {
}
});

let warnings: Record<string, import('$lib/tutorial').Warning[]> = {};
let warnings: Record<string, import('svelte/compiler').Warning[]> = {};
let timeout: any;

function schedule_to_update_warning(msec: number) {
Expand All @@ -63,13 +65,20 @@ export async function create(): Promise<Adapter> {
state.logs = [];
} else if (chunk?.startsWith('svelte:warnings:')) {
const warn: Warning = JSON.parse(chunk.slice(16));
const filename = warn.filename.startsWith('/') ? warn.filename : '/' + warn.filename;
const filename = (warn.filename!.startsWith('/') ? warn.filename : '/' + warn.filename)!;
const current = warnings[filename];

if (!current) {
warnings[filename] = [warn];
// the exact same warning may be given multiple times in a row
} else if (!current.some((s) => s.code === warn.code && s.pos === warn.pos)) {
} else if (
!current.some(
(s) =>
s.code === warn.code &&
s.position![0] === warn.position![0] &&
s.position![1] === warn.position![1]
)
) {
current.push(warn);
}

Expand Down
11 changes: 0 additions & 11 deletions apps/svelte.dev/src/lib/tutorial/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,17 +64,6 @@ export interface EditingConstraints {
remove: Set<string>;
}

// TODO replace with `Warning` from `svelte/compiler`
export interface Warning {
code: string;
start: { line: number; column: number; character: number };
end: { line: number; column: number; character: number };
pos: number;
filename: string;
frame: string;
message: string;
}

export interface MenuItem {
icon: string;
label: string;
Expand Down
1 change: 1 addition & 0 deletions apps/svelte.dev/src/routes/tutorial/[...slug]/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,7 @@
<section slot="b" class="editor-container">
<Editor
bind:this={editor}
errors={adapter.adapter_state.errors}
warnings={adapter.adapter_state.warnings}
{workspace}
onchange={async (file, contents) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ export const adapter_state = new (class {
status: 'initialising'
}
);
/** Compiler warnings */

/** Diagnostics */
errors = $derived((use_rollup ? rollup_state.errors : wc_state.errors) || {});
warnings = $derived((use_rollup ? rollup_state.warnings : wc_state.warnings) || {});
})();

Expand Down
67 changes: 47 additions & 20 deletions packages/editor/src/lib/Editor.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,18 @@
import { autocomplete_for_svelte } from '@sveltejs/site-kit/codemirror';
import type { Diagnostic } from '@codemirror/lint';
import { Workspace, type Item, type File } from './Workspace.svelte.js';
import type { Warning } from 'svelte/compiler';
import type { CompileError, Warning } from 'svelte/compiler';
import './codemirror.css';

interface Props {
warnings: Record<string, Warning[]>; // TODO this should include errors as well
errors: Record<string, CompileError | null>;
warnings: Record<string, Warning[]>;
workspace: Workspace;
onchange: (file: File, contents: string) => void;
autocomplete_filter?: (file: File) => boolean;
}

let { warnings, workspace, onchange, autocomplete_filter = () => true }: Props = $props();
let { errors, warnings, workspace, onchange, autocomplete_filter = () => true }: Props = $props();

let container: HTMLDivElement;

Expand Down Expand Up @@ -166,25 +167,51 @@
});

$effect(() => {
if (editor_view) {
if (workspace.selected_name) {
const current_warnings = warnings[workspace.selected_name] || [];
const diagnostics = current_warnings.map((warning) => {
/** @type {import('@codemirror/lint').Diagnostic} */
const diagnostic: Diagnostic = {
from: warning.start!.character,
to: warning.end!.character,
severity: 'warning',
message: warning.message
};

return diagnostic;
});
if (!editor_view || !workspace.selected_name) return;

const diagnostics: Diagnostic[] = [];

const error = null; // TODO should be `errors[workspace.selected_name]` but it's currently a Rollup plugin error...
const current_warnings = warnings[workspace.selected_name] || [];

if (error) {
// diagnostics.push({
// severity: 'error',
// from: error.position![0],
// to: error.position![1],
// message: error.message,
// renderMessage: () => {
// // TODO expose error codes, so we can link to docs in future
// const span = document.createElement('span');
// span.innerHTML = `${error.message
// .replace(/&/g, '&amp;')
// .replace(/</g, '&lt;')
// .replace(/`(.+?)`/g, `<code>$1</code>`)}`;
// return span;
// }
// });
}

const transaction = setDiagnostics(editor_view.state, diagnostics);
editor_view.dispatch(transaction);
}
for (const warning of current_warnings) {
diagnostics.push({
severity: 'warning',
from: warning.start!.character,
to: warning.end!.character,
message: warning.message,
renderMessage: () => {
const span = document.createElement('span');
span.innerHTML = `${warning.message
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/`(.+?)`/g, `<code>$1</code>`)} <strong>(${warning.code})</strong>`;

return span;
}
});
}

const transaction = setDiagnostics(editor_view.state, diagnostics);
editor_view.dispatch(transaction);
});
</script>

Expand Down
Loading
Loading