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) { export function compile(source, options) {
source = remove_bom(source); source = remove_bom(source);
state.reset_warnings(options.warningFilter); state.reset({ warning: options.warningFilter, filename: options.filename });
const validated = validate_component_options(options, ''); const validated = validate_component_options(options, '');
let parsed = _parse(source); let parsed = _parse(source);
@ -63,7 +63,7 @@ export function compile(source, options) {
*/ */
export function compileModule(source, options) { export function compileModule(source, options) {
source = remove_bom(source); source = remove_bom(source);
state.reset_warnings(options.warningFilter); state.reset({ warning: options.warningFilter, filename: options.filename });
const validated = validate_module_options(options, ''); const validated = validate_module_options(options, '');
const analysis = analyze_module(source, validated); const analysis = analyze_module(source, validated);
@ -111,7 +111,7 @@ export function compileModule(source, options) {
*/ */
export function parse(source, { modern, loose } = {}) { export function parse(source, { modern, loose } = {}) {
source = remove_bom(source); source = remove_bom(source);
state.reset_warnings(() => false); state.reset({ warning: () => false, filename: undefined });
const ast = _parse(source, loose); const ast = _parse(source, loose);
return to_public_ast(source, ast, modern); 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 { regex_valid_component_name } from '../phases/1-parse/state/element.js';
import { analyze_component } from '../phases/2-analyze/index.js'; import { analyze_component } from '../phases/2-analyze/index.js';
import { get_rune } from '../phases/scope.js'; import { get_rune } from '../phases/scope.js';
import { reset, reset_warnings } from '../state.js'; import { reset, UNKNOWN_FILENAME } from '../state.js';
import { import {
extract_identifiers, extract_identifiers,
extract_all_identifiers_from_expression, extract_all_identifiers_from_expression,
@ -134,7 +134,7 @@ export function migrate(source, { filename, use_ts } = {}) {
return start + style_placeholder + end; return start + style_placeholder + end;
}); });
reset_warnings(() => false); reset({ warning: () => false, filename });
let parsed = parse(source); let parsed = parse(source);
@ -145,7 +145,7 @@ export function migrate(source, { filename, use_ts } = {}) {
...validate_component_options({}, ''), ...validate_component_options({}, ''),
...parsed_options, ...parsed_options,
customElementOptions, customElementOptions,
filename: filename ?? '(unknown)', filename: filename ?? UNKNOWN_FILENAME,
experimental: { experimental: {
async: true async: true
} }

@ -279,9 +279,8 @@ export function analyze_module(source, options) {
classes: new Map() classes: new Map()
}; };
state.reset({ state.adjust({
dev: options.dev, dev: options.dev,
filename: options.filename,
rootDir: options.rootDir, rootDir: options.rootDir,
runes: true runes: true
}); });
@ -531,12 +530,11 @@ export function analyze_component(root, source, options) {
async_deriveds: new Set() async_deriveds: new Set()
}; };
state.reset({ state.adjust({
component_name: analysis.name, component_name: analysis.name,
dev: options.dev, dev: options.dev,
filename: options.filename,
rootDir: options.rootDir, rootDir: options.rootDir,
runes: true runes
}); });
if (!runes) { if (!runes) {

@ -3,7 +3,7 @@
import { visit_component } from './shared/component.js'; import { visit_component } from './shared/component.js';
import * as e from '../../../errors.js'; import * as e from '../../../errors.js';
import * as w from '../../../warnings.js'; import * as w from '../../../warnings.js';
import { filename } from '../../../state.js'; import { filename, UNKNOWN_FILENAME } from '../../../state.js';
/** /**
* @param {AST.SvelteSelf} node * @param {AST.SvelteSelf} node
@ -23,9 +23,9 @@ export function SvelteSelf(node, context) {
} }
if (context.state.analysis.runes) { 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 = const basename =
filename === '(unknown)' filename === UNKNOWN_FILENAME
? 'Self.svelte' ? 'Self.svelte'
: /** @type {string} */ (filename.split(/[/\\]/).pop()); : /** @type {string} */ (filename.split(/[/\\]/).pop());

@ -16,6 +16,11 @@ export let warnings = [];
*/ */
export let filename; 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. * 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(); ignore_stack.pop();
} }
/**
*
* @param {(warning: Warning) => boolean} fn
*/
export function reset_warnings(fn = () => true) {
warning_filter = fn;
warnings = [];
}
/** /**
* @param {AST.SvelteNode | NodeLike} node * @param {AST.SvelteNode | NodeLike} node
* @param {import('../constants.js').IGNORABLE_RUNTIME_WARNINGS[number]} code * @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 {{ * @param {{
* dev: boolean; * dev: boolean;
* filename: string;
* component_name?: string; * component_name?: string;
* rootDir?: string; * rootDir?: string;
* runes: boolean; * runes: boolean;
* }} state * }} state
*/ */
export function reset(state) { export function adjust(state) {
const root_dir = state.rootDir?.replace(/\\/g, '/'); const root_dir = state.rootDir?.replace(/\\/g, '/');
filename = state.filename.replace(/\\/g, '/');
dev = state.dev; dev = state.dev;
runes = state.runes; 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)) { if (typeof root_dir === 'string' && filename.startsWith(root_dir)) {
// make filename relative to rootDir // make filename relative to rootDir

@ -61,7 +61,7 @@ export class CompileDiagnostic {
this.code = code; this.code = code;
this.message = message; this.message = message;
if (state.filename) { if (state.filename !== state.UNKNOWN_FILENAME) {
this.filename = state.filename; this.filename = state.filename;
} }

@ -1,5 +1,5 @@
import * as fs from 'node:fs'; 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 { compile, compileModule, type CompileError } from 'svelte/compiler';
import { suite, type BaseTest } from '../suite'; import { suite, type BaseTest } from '../suite';
import { read_file } from '../helpers.js'; import { read_file } from '../helpers.js';
@ -78,3 +78,15 @@ const { test, run } = suite<CompilerErrorTest>((config, cwd) => {
export { test }; export { test };
await run(__dirname); 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