feat: add ability to ignore warnings through compiler option (#12296)

* feat: add ability to ignore warnings through compiler option

Some people want to disable certain warnings for their whole application without having to add svelte-ignore comments everywhere. This new option makes that possible.
closes #9188
part of https://github.com/sveltejs/language-tools/issues/650

* make it a function

* singular

* warningFilter

* make internal filter non-nullable

* Update .changeset/little-seals-reflect.md

* filter_warning -> warning_filter, for symmetry with public option

* fix

---------

Co-authored-by: Rich Harris <rich.harris@vercel.com>
pull/12456/head
Simon H 5 months ago committed by GitHub
parent c92620dcbe
commit d9569d052e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
feat: add ability to ignore warnings through `warningFilter` compiler option

@ -1,4 +1,4 @@
import { warnings, ignore_stack, ignore_map } from './state.js'; import { warnings, ignore_stack, ignore_map, warning_filter } from './state.js';
import { CompileDiagnostic } from './utils/compile_diagnostic.js'; import { CompileDiagnostic } from './utils/compile_diagnostic.js';
/** @typedef {{ start?: number, end?: number }} NodeLike */ /** @typedef {{ start?: number, end?: number }} NodeLike */
@ -28,13 +28,15 @@ function w(node, code, message) {
} }
if (stack && stack.at(-1)?.has(code)) return; if (stack && stack.at(-1)?.has(code)) return;
warnings.push( const warning = new InternalCompileWarning(
new InternalCompileWarning( code,
code, message,
message, node && node.start !== undefined ? [node.start, node.end ?? node.start] : undefined
node && node.start !== undefined ? [node.start, node.end ?? node.start] : undefined
)
); );
if (!warning_filter(warning)) return;
warnings.push(warning);
} }
export const codes = CODES; export const codes = CODES;

@ -20,6 +20,7 @@ export { default as preprocess } from './preprocess/index.js';
* @returns {CompileResult} * @returns {CompileResult}
*/ */
export function compile(source, options) { export function compile(source, options) {
state.reset_warning_filter(options.warningFilter);
const validated = validate_component_options(options, ''); const validated = validate_component_options(options, '');
state.reset(source, validated); state.reset(source, validated);
@ -58,6 +59,7 @@ export function compile(source, options) {
* @returns {CompileResult} * @returns {CompileResult}
*/ */
export function compileModule(source, options) { export function compileModule(source, options) {
state.reset_warning_filter(options.warningFilter);
const validated = validate_module_options(options, ''); const validated = validate_module_options(options, '');
state.reset(source, validated); state.reset(source, validated);
@ -103,6 +105,7 @@ export function compileModule(source, options) {
* @returns {Root | LegacyRoot} * @returns {Root | LegacyRoot}
*/ */
export function parse(source, { filename, rootDir, modern } = {}) { export function parse(source, { filename, rootDir, modern } = {}) {
state.reset_warning_filter(() => false);
state.reset(source, { filename, rootDir }); // TODO it's weird to require filename/rootDir here. reconsider the API state.reset(source, { filename, rootDir }); // TODO it's weird to require filename/rootDir here. reconsider the API
const ast = _parse(source); const ast = _parse(source);

@ -10,7 +10,7 @@ import { parse } from '../phases/1-parse/index.js';
import { analyze_component } from '../phases/2-analyze/index.js'; import { analyze_component } from '../phases/2-analyze/index.js';
import { validate_component_options } from '../validate-options.js'; import { validate_component_options } from '../validate-options.js';
import { get_rune } from '../phases/scope.js'; import { get_rune } from '../phases/scope.js';
import { reset } from '../state.js'; import { reset, reset_warning_filter } from '../state.js';
import { extract_identifiers } from '../utils/ast.js'; import { extract_identifiers } from '../utils/ast.js';
import { regex_is_valid_identifier } from '../phases/patterns.js'; import { regex_is_valid_identifier } from '../phases/patterns.js';
import { migrate_svelte_ignore } from '../utils/extract_svelte_ignore.js'; import { migrate_svelte_ignore } from '../utils/extract_svelte_ignore.js';
@ -24,6 +24,7 @@ import { migrate_svelte_ignore } from '../utils/extract_svelte_ignore.js';
*/ */
export function migrate(source) { export function migrate(source) {
try { try {
reset_warning_filter(() => false);
reset(source, { filename: 'migrate.svelte' }); reset(source, { filename: 'migrate.svelte' });
let parsed = parse(source); let parsed = parse(source);

@ -1,4 +1,4 @@
/** @import { SvelteNode } from './types' */ /** @import { CompileOptions, SvelteNode } from './types' */
/** @import { Warning } from '#compiler' */ /** @import { Warning } from '#compiler' */
import { getLocator } from 'locate-character'; import { getLocator } from 'locate-character';
@ -22,6 +22,9 @@ export let source;
export let locator = getLocator('', { offsetLine: 1 }); export let locator = getLocator('', { offsetLine: 1 });
/** @type {NonNullable<CompileOptions['warningFilter']>} */
export let warning_filter;
/** /**
* The current stack of ignored warnings * The current stack of ignored warnings
* @type {Set<string>[]} * @type {Set<string>[]}
@ -48,6 +51,14 @@ export function pop_ignore() {
ignore_stack.pop(); ignore_stack.pop();
} }
/**
*
* @param {(warning: Warning) => boolean} fn
*/
export function reset_warning_filter(fn = () => true) {
warning_filter = fn;
}
/** /**
* @param {string} _source * @param {string} _source
* @param {{ filename?: string, rootDir?: string }} options * @param {{ filename?: string, rootDir?: string }} options

@ -203,12 +203,16 @@ export interface ModuleCompileOptions {
* Used for debugging hints and sourcemaps. Your bundler plugin will set it automatically. * Used for debugging hints and sourcemaps. Your bundler plugin will set it automatically.
*/ */
filename?: string; filename?: string;
/** /**
* Used for ensuring filenames don't leak filesystem information. Your bundler plugin will set it automatically. * Used for ensuring filenames don't leak filesystem information. Your bundler plugin will set it automatically.
* @default process.cwd() on node-like environments, undefined elsewhere * @default process.cwd() on node-like environments, undefined elsewhere
*/ */
rootDir?: string; rootDir?: string;
/**
* A function that gets a `Warning` as an argument and returns a boolean.
* Use this to filter out warnings. Return `true` to keep the warning, `false` to discard it.
*/
warningFilter?: (warning: Warning) => boolean;
} }
// The following two somewhat scary looking types ensure that certain types are required but can be undefined still // The following two somewhat scary looking types ensure that certain types are required but can be undefined still

@ -104,6 +104,8 @@ export const validate_component_options =
hmr: boolean(false), hmr: boolean(false),
warningFilter: fun(() => true),
sourcemap: validator(undefined, (input) => { sourcemap: validator(undefined, (input) => {
// Source maps can take on a variety of values, including string, JSON, map objects from magic-string and source-map, // Source maps can take on a variety of values, including string, JSON, map objects from magic-string and source-map,
// so there's no good way to check type validity here // so there's no good way to check type validity here
@ -259,6 +261,20 @@ function string(fallback, allow_empty = true) {
}); });
} }
/**
* @param {string[]} fallback
* @returns {Validator}
*/
function string_array(fallback) {
return validator(fallback, (input, keypath) => {
if (input && !Array.isArray(input)) {
throw_error(`${keypath} should be a string array, if specified`);
}
return input;
});
}
/** /**
* @param {boolean | undefined} fallback * @param {boolean | undefined} fallback
* @returns {Validator} * @returns {Validator}

@ -1,6 +1,12 @@
/* This file is generated by scripts/process-messages/index.js. Do not edit! */ /* This file is generated by scripts/process-messages/index.js. Do not edit! */
import { warnings, ignore_stack, ignore_map } from './state.js'; import {
warnings,
ignore_stack,
ignore_map,
warning_filter
} from './state.js';
import { CompileDiagnostic } from './utils/compile_diagnostic.js'; import { CompileDiagnostic } from './utils/compile_diagnostic.js';
/** @typedef {{ start?: number, end?: number }} NodeLike */ /** @typedef {{ start?: number, end?: number }} NodeLike */
@ -30,7 +36,11 @@ function w(node, code, message) {
} }
if (stack && stack.at(-1)?.has(code)) return; if (stack && stack.at(-1)?.has(code)) return;
warnings.push(new InternalCompileWarning(code, message, node && node.start !== undefined ? [node.start, node.end ?? node.start] : undefined));
const warning = new InternalCompileWarning(code, message, node && node.start !== undefined ? [node.start, node.end ?? node.start] : undefined);
if (!warning_filter(warning)) return;
warnings.push(warning);
} }
export const codes = [ export const codes = [

@ -0,0 +1,8 @@
import { test } from '../../test';
export default test({
compileOptions: {
warningFilter: (warning) =>
!['a11y_missing_attribute', 'a11y_misplaced_scope'].includes(warning.code)
}
});

@ -0,0 +1,8 @@
<div>
<img src="this-is-fine.jpg" />
<marquee>but this is still discouraged</marquee>
</div>
<div scope></div>
<img src="potato.jpg" />

@ -0,0 +1,14 @@
[
{
"code": "a11y_distracting_elements",
"end": {
"column": 49,
"line": 3
},
"message": "Avoid `<marquee>` elements",
"start": {
"column": 1,
"line": 3
}
}
]

@ -864,12 +864,16 @@ declare module 'svelte/compiler' {
* Used for debugging hints and sourcemaps. Your bundler plugin will set it automatically. * Used for debugging hints and sourcemaps. Your bundler plugin will set it automatically.
*/ */
filename?: string; filename?: string;
/** /**
* Used for ensuring filenames don't leak filesystem information. Your bundler plugin will set it automatically. * Used for ensuring filenames don't leak filesystem information. Your bundler plugin will set it automatically.
* @default process.cwd() on node-like environments, undefined elsewhere * @default process.cwd() on node-like environments, undefined elsewhere
*/ */
rootDir?: string; rootDir?: string;
/**
* A function that gets a `Warning` as an argument and returns a boolean.
* Use this to filter out warnings. Return `true` to keep the warning, `false` to discard it.
*/
warningFilter?: (warning: Warning) => boolean;
} }
type DeclarationKind = type DeclarationKind =
@ -2673,12 +2677,16 @@ declare module 'svelte/types/compiler/interfaces' {
* Used for debugging hints and sourcemaps. Your bundler plugin will set it automatically. * Used for debugging hints and sourcemaps. Your bundler plugin will set it automatically.
*/ */
filename?: string; filename?: string;
/** /**
* Used for ensuring filenames don't leak filesystem information. Your bundler plugin will set it automatically. * Used for ensuring filenames don't leak filesystem information. Your bundler plugin will set it automatically.
* @default process.cwd() on node-like environments, undefined elsewhere * @default process.cwd() on node-like environments, undefined elsewhere
*/ */
rootDir?: string; rootDir?: string;
/**
* A function that gets a `Warning` as an argument and returns a boolean.
* Use this to filter out warnings. Return `true` to keep the warning, `false` to discard it.
*/
warningFilter?: (warning: Warning_1) => boolean;
} }
/** /**
* - `html` the default, for e.g. `<div>` or `<span>` * - `html` the default, for e.g. `<div>` or `<span>`

Loading…
Cancel
Save