fix: don't emit HTML warnings in custom renderer compile

pull/18058/head
paoloricciuti 4 months ago
parent 883611fd8b
commit 801c8ef4ae

@ -11,7 +11,7 @@ import { mark_subtree_dynamic } from './shared/fragment.js';
export function ExpressionTag(node, context) {
const in_template = context.path.at(-1)?.type === 'Fragment';
if (in_template && context.state.parent_element) {
if (in_template && context.state.parent_element && !context.state.analysis.custom_renderer) {
const message = is_tag_valid_with_parent('#text', context.state.parent_element);
if (message) {
e.node_invalid_placement(node, message);

@ -25,7 +25,10 @@ import { runes } from '../../../state.js';
*/
export function RegularElement(node, context) {
validate_element(node, context);
check_element(node, context);
if (!context.state.analysis.custom_renderer) {
check_element(node, context);
}
node.metadata.path = [...context.path];
context.state.analysis.elements.push(node);
@ -34,7 +37,9 @@ export function RegularElement(node, context) {
if (node.name === 'textarea' && node.fragment.nodes.length > 0) {
for (const attribute of node.attributes) {
if (attribute.type === 'Attribute' && attribute.name === 'value') {
e.textarea_invalid_content(node);
if (!context.state.analysis.custom_renderer) {
e.textarea_invalid_content(node);
}
}
}
@ -160,7 +165,7 @@ export function RegularElement(node, context) {
mark_subtree_dynamic(context.path);
}
if (context.state.parent_element) {
if (context.state.parent_element && !context.state.analysis.custom_renderer) {
let past_parent = false;
let only_warn = false;
const ancestors = [context.state.parent_element];
@ -221,7 +226,8 @@ export function RegularElement(node, context) {
context.state.analysis.source[node.end - 2] === '/' &&
!is_void(node_name) &&
!is_svg(node_name) &&
!is_mathml(node_name)
!is_mathml(node_name) &&
!context.state.analysis.custom_renderer
) {
w.element_invalid_self_closing_tag(node, node.name);
}

@ -12,7 +12,10 @@ import { mark_subtree_dynamic } from './shared/fragment.js';
*/
export function SvelteElement(node, context) {
validate_element(node, context);
check_element(node, context);
if (!context.state.analysis.custom_renderer) {
check_element(node, context);
}
node.metadata.path = [...context.path];
context.state.analysis.elements.push(node);

@ -16,7 +16,8 @@ export function Text(node, context) {
if (
parent.type === 'Fragment' &&
context.state.parent_element &&
regex_not_whitespace.test(node.data)
regex_not_whitespace.test(node.data) &&
!context.state.analysis.custom_renderer
) {
const message = is_tag_valid_with_parent('#text', context.state.parent_element);
if (message) {

@ -79,12 +79,12 @@ export function validate_element(node, context) {
validate_slot_attribute(context, attribute);
}
if (attribute.name === 'is') {
if (attribute.name === 'is' && !context.state.analysis.custom_renderer) {
w.attribute_avoid_is(attribute);
}
const correct_name = react_attributes.get(attribute.name);
if (correct_name) {
if (correct_name && !context.state.analysis.custom_renderer) {
w.attribute_invalid_property_name(attribute, attribute.name, correct_name);
}

@ -0,0 +1,9 @@
import { test } from '../../test';
// This test verifies that HTML-specific warnings and errors are NOT produced
// when compiling with a custom renderer. All patterns in main.svelte would
// normally trigger compile errors or warnings in standard HTML mode.
// With a custom renderer, they should all be silently accepted.
export default test({
compile_warnings: false
});

@ -0,0 +1,79 @@
<script>
let text = $state('expression in table');
let show = $state(true);
</script>
<!-- 1. node_invalid_placement: <a> containing <a> -->
<a><a>link</a></a>
<!-- 2. node_invalid_placement: text inside <table> -->
<table>text inside table</table>
<!-- 3. node_invalid_placement: expression tag inside <table> -->
<table>{text}</table>
<!-- 4. element_invalid_self_closing_tag: self-closing non-void element -->
<div />
<!-- 5. textarea_invalid_content: textarea with both value attribute and children -->
<textarea value={text}>child</textarea>
<!-- 6. attribute_avoid_is: the `is` attribute -->
<button is="my-button">custom element</button>
<!-- 7. attribute_invalid_property_name: React-style className -->
<div className="foo">className test</div>
<!-- 8. attribute_invalid_property_name: React-style htmlFor -->
<label htmlFor="input-id">htmlFor test</label>
<!-- 9. a11y_accesskey: accesskey attribute -->
<div accesskey="s">accesskey test</div>
<!-- 10. a11y_autofocus: autofocus on input -->
<input autofocus />
<!-- 11. a11y_misplaced_scope: scope on div -->
<div scope="col">misplaced scope</div>
<!-- 12. a11y_positive_tabindex: positive tabindex -->
<div tabindex="5">positive tabindex</div>
<!-- 13. a11y_hidden: aria-hidden on heading -->
<h1 aria-hidden="true">hidden heading</h1>
<!-- 14. a11y_missing_attribute: img without alt -->
<img src="test.png" />
<!-- 15. a11y_unknown_aria_attribute: unknown aria attribute -->
<div aria-describedat="foo">unknown aria</div>
<!-- 16. a11y_no_redundant_roles: redundant role on nav -->
<nav role="navigation">redundant role</nav>
<!-- 17. a11y_figcaption_parent: figcaption outside figure -->
<figcaption>orphan figcaption</figcaption>
<!-- 18. a11y_distracting_elements: marquee -->
<marquee>distracting</marquee>
<!-- 19. a11y_consider_explicit_label: <a> without text -->
<a href="https://example.com"></a>
<!-- 20. a11y_label_has_associated_control: label without associated control -->
<label>no control</label>
<!-- 21. a11y_missing_content: empty h2 -->
<h2></h2>
<!-- 22. node_invalid_placement_ssr: invalid nesting inside conditional block -->
<div>
{#if show}
<tr><td>conditional nesting</td></tr>
{/if}
</div>
<!-- 23. a11y via svelte:element: a11y warnings should also be suppressed on <svelte:element> -->
<svelte:element this={"img"} src="test.png" />
<svelte:element this={"div"} accesskey="s">accesskey on svelte:element</svelte:element>
<svelte:element this={"h1"} aria-hidden="true">hidden svelte:element heading</svelte:element>

@ -1,3 +1,4 @@
import * as fs from 'node:fs';
import * as path from 'node:path';
import { setImmediate } from 'node:timers/promises';
import { assert } from 'vitest';
@ -6,6 +7,7 @@ import { suite_with_variants, type BaseTest } from '../suite.js';
import type { CompileOptions } from '#compiler';
import renderer, { create_root, serialize, dispatch_event, dom_elements } from './renderer.js';
import { writeFile } from 'node:fs/promises';
import { globSync } from 'tinyglobby';
export interface CustomRendererTest extends BaseTest {
html?: string;
@ -14,6 +16,7 @@ export interface CustomRendererTest extends BaseTest {
context?: Map<any, any>;
error?: string;
compile_error?: string;
compile_warnings?: false;
runtime_error?: string;
warnings?: string[];
test?: (args: {
@ -85,6 +88,20 @@ async function common_setup(
throw err;
}
if (config.compile_warnings === false) {
const output_dir = `${cwd}/_output/client`;
const warning_files = globSync('**/*.warnings.json', { cwd: output_dir });
for (const file of warning_files) {
const warnings = JSON.parse(fs.readFileSync(path.join(output_dir, file), 'utf-8'));
assert.deepEqual(
warnings,
[],
`Expected no compile warnings in ${file}, got: ${warnings.map((/** @type {any} */ w: any) => w.code).join(', ')}`
);
}
}
return compile_options;
}

Loading…
Cancel
Save