diff --git a/.changeset/two-terms-draw.md b/.changeset/two-terms-draw.md new file mode 100644 index 0000000000..88ef4be86e --- /dev/null +++ b/.changeset/two-terms-draw.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: ensure compiler state is reset before compilation diff --git a/packages/svelte/src/compiler/index.js b/packages/svelte/src/compiler/index.js index 9ba23c1485..a378af34ee 100644 --- a/packages/svelte/src/compiler/index.js +++ b/packages/svelte/src/compiler/index.js @@ -20,7 +20,7 @@ export { default as preprocess } from './preprocess/index.js'; */ export function compile(source, options) { source = remove_bom(source); - state.reset_warnings(options.warningFilter); + state.reset({ warning: options.warningFilter, filename: options.filename }); const validated = validate_component_options(options, ''); let parsed = _parse(source); @@ -63,7 +63,7 @@ export function compile(source, options) { */ export function compileModule(source, options) { source = remove_bom(source); - state.reset_warnings(options.warningFilter); + state.reset({ warning: options.warningFilter, filename: options.filename }); const validated = validate_module_options(options, ''); const analysis = analyze_module(source, validated); @@ -111,7 +111,7 @@ export function compileModule(source, options) { */ export function parse(source, { modern, loose } = {}) { source = remove_bom(source); - state.reset_warnings(() => false); + state.reset({ warning: () => false, filename: undefined }); const ast = _parse(source, loose); return to_public_ast(source, ast, modern); diff --git a/packages/svelte/src/compiler/migrate/index.js b/packages/svelte/src/compiler/migrate/index.js index 0e2fe019ed..6b2e6cda70 100644 --- a/packages/svelte/src/compiler/migrate/index.js +++ b/packages/svelte/src/compiler/migrate/index.js @@ -9,7 +9,7 @@ import { parse } from '../phases/1-parse/index.js'; import { regex_valid_component_name } from '../phases/1-parse/state/element.js'; import { analyze_component } from '../phases/2-analyze/index.js'; import { get_rune } from '../phases/scope.js'; -import { reset, reset_warnings } from '../state.js'; +import { reset, UNKNOWN_FILENAME } from '../state.js'; import { extract_identifiers, extract_all_identifiers_from_expression, @@ -134,7 +134,7 @@ export function migrate(source, { filename, use_ts } = {}) { return start + style_placeholder + end; }); - reset_warnings(() => false); + reset({ warning: () => false, filename }); let parsed = parse(source); @@ -145,7 +145,7 @@ export function migrate(source, { filename, use_ts } = {}) { ...validate_component_options({}, ''), ...parsed_options, customElementOptions, - filename: filename ?? '(unknown)', + filename: filename ?? UNKNOWN_FILENAME, experimental: { async: true } diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js index 258b59018a..d407b44556 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/index.js @@ -279,9 +279,8 @@ export function analyze_module(source, options) { classes: new Map() }; - state.reset({ + state.adjust({ dev: options.dev, - filename: options.filename, rootDir: options.rootDir, runes: true }); @@ -531,12 +530,11 @@ export function analyze_component(root, source, options) { async_deriveds: new Set() }; - state.reset({ + state.adjust({ component_name: analysis.name, dev: options.dev, - filename: options.filename, rootDir: options.rootDir, - runes: true + runes }); if (!runes) { diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/SvelteSelf.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/SvelteSelf.js index b87f082de0..652a447165 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/SvelteSelf.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/SvelteSelf.js @@ -3,7 +3,7 @@ import { visit_component } from './shared/component.js'; import * as e from '../../../errors.js'; import * as w from '../../../warnings.js'; -import { filename } from '../../../state.js'; +import { filename, UNKNOWN_FILENAME } from '../../../state.js'; /** * @param {AST.SvelteSelf} node @@ -23,9 +23,9 @@ export function SvelteSelf(node, context) { } if (context.state.analysis.runes) { - const name = filename === '(unknown)' ? 'Self' : context.state.analysis.name; + const name = filename === UNKNOWN_FILENAME ? 'Self' : context.state.analysis.name; const basename = - filename === '(unknown)' + filename === UNKNOWN_FILENAME ? 'Self.svelte' : /** @type {string} */ (filename.split(/[/\\]/).pop()); diff --git a/packages/svelte/src/compiler/state.js b/packages/svelte/src/compiler/state.js index 5eb25dd6bb..725d03b802 100644 --- a/packages/svelte/src/compiler/state.js +++ b/packages/svelte/src/compiler/state.js @@ -16,6 +16,11 @@ export let warnings = []; */ export let filename; +/** + * This is the fallback used when no filename is specified. + */ +export const UNKNOWN_FILENAME = '(unknown)'; + /** * The name of the component that is used in the `export default function ...` statement. */ @@ -80,15 +85,6 @@ export function pop_ignore() { ignore_stack.pop(); } -/** - * - * @param {(warning: Warning) => boolean} fn - */ -export function reset_warnings(fn = () => true) { - warning_filter = fn; - warnings = []; -} - /** * @param {AST.SvelteNode | NodeLike} node * @param {import('../constants.js').IGNORABLE_RUNTIME_WARNINGS[number]} code @@ -99,21 +95,36 @@ export function is_ignored(node, code) { } /** + * Call this to reset the compiler state. Should be called before each compilation. + * @param {{ warning?: (warning: Warning) => boolean; filename: string | undefined }} state + */ +export function reset(state) { + dev = false; + runes = false; + component_name = UNKNOWN_FILENAME; + source = ''; + locator = () => undefined; + filename = (state.filename ?? UNKNOWN_FILENAME).replace(/\\/g, '/'); + warning_filter = state.warning ?? (() => true); + warnings = []; +} + +/** + * Adjust the compiler state based on the provided state object. + * Call this after parsing and basic analysis happened. * @param {{ * dev: boolean; - * filename: string; * component_name?: string; * rootDir?: string; * runes: boolean; * }} state */ -export function reset(state) { +export function adjust(state) { const root_dir = state.rootDir?.replace(/\\/g, '/'); - filename = state.filename.replace(/\\/g, '/'); dev = state.dev; runes = state.runes; - component_name = state.component_name ?? '(unknown)'; + component_name = state.component_name ?? UNKNOWN_FILENAME; if (typeof root_dir === 'string' && filename.startsWith(root_dir)) { // make filename relative to rootDir diff --git a/packages/svelte/src/compiler/utils/compile_diagnostic.js b/packages/svelte/src/compiler/utils/compile_diagnostic.js index db938cf2bd..c5df49e01c 100644 --- a/packages/svelte/src/compiler/utils/compile_diagnostic.js +++ b/packages/svelte/src/compiler/utils/compile_diagnostic.js @@ -61,7 +61,7 @@ export class CompileDiagnostic { this.code = code; this.message = message; - if (state.filename) { + if (state.filename !== state.UNKNOWN_FILENAME) { this.filename = state.filename; } diff --git a/packages/svelte/tests/compiler-errors/test.ts b/packages/svelte/tests/compiler-errors/test.ts index 5e57a3a032..13b9280dde 100644 --- a/packages/svelte/tests/compiler-errors/test.ts +++ b/packages/svelte/tests/compiler-errors/test.ts @@ -1,5 +1,5 @@ import * as fs from 'node:fs'; -import { assert, expect } from 'vitest'; +import { assert, expect, it } from 'vitest'; import { compile, compileModule, type CompileError } from 'svelte/compiler'; import { suite, type BaseTest } from '../suite'; import { read_file } from '../helpers.js'; @@ -78,3 +78,15 @@ const { test, run } = suite((config, cwd) => { export { test }; await run(__dirname); + +it('resets the compiler state including filename', () => { + // start with something that succeeds + compile('
hello
', { filename: 'foo.svelte' }); + // then try something that fails in the parsing stage + try { + compile('

hello

invalid

', { filename: 'bar.svelte' }); + expect.fail('Expected an error'); + } catch (e: any) { + expect(e.toString()).toContain('bar.svelte'); + } +});