fix: ensure compiler state is reset before compilation (#16396)

#16268 introduced a slight regression where the state is not reset completely upon compilation. It did reset warnings but not other state, which meant if file A succeeds but file B fails in the parsing state (before the state was reset for real) it would get wrong filename info. This fixes it by setting the filename at the very beginning.
pull/16397/head
Simon H 2 months ago committed by GitHub
parent 4947283fa5
commit ee1ef6083a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: ensure compiler state is reset before compilation

@ -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);

@ -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
}

@ -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) {

@ -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());

@ -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

@ -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;
}

@ -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<CompilerErrorTest>((config, cwd) => {
export { test };
await run(__dirname);
it('resets the compiler state including filename', () => {
// start with something that succeeds
compile('<div>hello</div>', { filename: 'foo.svelte' });
// then try something that fails in the parsing stage
try {
compile('<p>hello<div>invalid</p>', { filename: 'bar.svelte' });
expect.fail('Expected an error');
} catch (e: any) {
expect(e.toString()).toContain('bar.svelte');
}
});

Loading…
Cancel
Save