Skip to content

Commit 5cb432b

Browse files
feat: warn on unknown warning codes in runes mode (#11549)
Related to #11414 --------- Co-authored-by: Simon Holthausen <[email protected]>
1 parent f6b8004 commit 5cb432b

File tree

19 files changed

+308
-27
lines changed

19 files changed

+308
-27
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
## legacy_code
2+
3+
> `%code%` is no longer valid — please use `%suggestion%` instead
4+
5+
## unknown_code
6+
7+
> `%code%` is not a recognised code
8+
9+
> `%code%` is not a recognised code (did you mean `%suggestion%`?)

packages/svelte/scripts/process-messages/index.js

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ function transform(name, dest) {
6161

6262
const comments = [];
6363

64-
const ast = acorn.parse(source, {
64+
let ast = acorn.parse(source, {
6565
ecmaVersion: 'latest',
6666
sourceType: 'module',
6767
onComment: (block, value, start, end) => {
@@ -80,7 +80,7 @@ function transform(name, dest) {
8080
}
8181
});
8282

83-
walk(ast, null, {
83+
ast = walk(ast, null, {
8484
_(node, { next }) {
8585
let comment;
8686

@@ -100,6 +100,18 @@ function transform(name, dest) {
100100
node.trailingComments = [comments.shift()];
101101
}
102102
}
103+
},
104+
// @ts-expect-error
105+
Identifier(node, context) {
106+
if (node.name === 'CODES') {
107+
return {
108+
type: 'ArrayExpression',
109+
elements: Object.keys(messages[name]).map((code) => ({
110+
type: 'Literal',
111+
value: code
112+
}))
113+
};
114+
}
103115
}
104116
});
105117

packages/svelte/scripts/process-messages/templates/compile-warnings.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ function w(node, code, message) {
4242
});
4343
}
4444

45+
export const codes = CODES;
46+
4547
/**
4648
* MESSAGE
4749
* @param {null | NodeLike} node

packages/svelte/src/compiler/legacy.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ export function convert(source, ast) {
201201
Comment(node) {
202202
return {
203203
...node,
204-
ignores: extract_svelte_ignore(node.data)
204+
ignores: extract_svelte_ignore(node.start, node.data, false)
205205
};
206206
},
207207
ComplexSelector(node) {

packages/svelte/src/compiler/phases/2-analyze/a11y.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -727,6 +727,9 @@ function check_element(node, state) {
727727
for (const attribute of node.attributes) {
728728
if (attribute.type !== 'Attribute') continue;
729729

730+
// @ts-expect-error gross hack
731+
attribute.ignores = node.ignores;
732+
730733
const name = attribute.name.toLowerCase();
731734
// aria-props
732735
if (name.startsWith('aria-')) {

packages/svelte/src/compiler/phases/2-analyze/index.js

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -559,10 +559,14 @@ export function analyze_component(root, source, options) {
559559
prune(analysis.css.ast, element);
560560
}
561561

562-
if (
563-
!analysis.css.ast.content.comment ||
564-
!extract_svelte_ignore(analysis.css.ast.content.comment.data).includes('css_unused_selector')
565-
) {
562+
const { comment } = analysis.css.ast.content;
563+
const should_ignore_unused =
564+
comment &&
565+
extract_svelte_ignore(comment.start, comment.data, analysis.runes).includes(
566+
'css_unused_selector'
567+
);
568+
569+
if (!should_ignore_unused) {
566570
warn_unused(analysis.css.ast);
567571
}
568572

@@ -1105,7 +1109,8 @@ const common_visitors = {
11051109
const ignores = [];
11061110

11071111
for (const comment of comments) {
1108-
ignores.push(...extract_svelte_ignore(comment.value));
1112+
const start = /** @type {any} */ (comment).start + 2;
1113+
ignores.push(...extract_svelte_ignore(start, comment.value, context.state.analysis.runes));
11091114
}
11101115

11111116
if (ignores.length > 0) {
@@ -1136,7 +1141,11 @@ const common_visitors = {
11361141
}
11371142

11381143
if (child.type === 'Comment') {
1139-
ignores.push(...extract_svelte_ignore(child.data));
1144+
const start =
1145+
child.start +
1146+
(context.state.analysis.source.slice(child.start, child.start + 4) === '<!--' ? 4 : 2);
1147+
1148+
ignores.push(...extract_svelte_ignore(start, child.data, context.state.analysis.runes));
11401149
} else {
11411150
const combined_ignores = new Set(context.state.ignores);
11421151
for (const ignore of ignores) combined_ignores.add(ignore);
Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,58 @@
1-
import { regex_whitespace } from '../phases/patterns.js';
1+
import fuzzymatch from '../phases/1-parse/utils/fuzzymatch.js';
2+
import * as w from '../warnings.js';
23

3-
const regex_svelte_ignore = /^\s*svelte-ignore\s+([\s\S]+)\s*$/m;
4+
const regex_svelte_ignore = /^\s*svelte-ignore\s/;
5+
6+
/** @type {Record<string, string>} */
7+
const replacements = {
8+
'non-top-level-reactive-declaration': 'reactive_declaration_invalid_placement'
9+
};
410

511
/**
12+
* @param {number} offset
613
* @param {string} text
14+
* @param {boolean} runes
715
* @returns {string[]}
816
*/
9-
export function extract_svelte_ignore(text) {
17+
export function extract_svelte_ignore(offset, text, runes) {
1018
const match = regex_svelte_ignore.exec(text);
11-
return match
12-
? match[1]
13-
.split(regex_whitespace)
14-
.map(/** @param {any} x */ (x) => x.trim())
15-
.filter(Boolean)
16-
: [];
19+
if (!match) return [];
20+
21+
let length = match[0].length;
22+
offset += length;
23+
24+
/** @type {string[]} */
25+
const ignores = [];
26+
27+
// Warnings have to be separated by commas, everything after is interpreted as prose
28+
for (const match of text.slice(length).matchAll(/([\w$-]+)(,)?/gm)) {
29+
const code = match[1];
30+
31+
ignores.push(code);
32+
33+
if (!w.codes.includes(code)) {
34+
const replacement = replacements[code] ?? code.replace(/-/g, '_');
35+
36+
if (runes) {
37+
// The type cast is for some reason necessary to pass the type check in CI
38+
const start = offset + /** @type {number} */ (match.index);
39+
const end = start + code.length;
40+
41+
if (w.codes.includes(replacement)) {
42+
w.legacy_code({ start, end }, code, replacement);
43+
} else {
44+
const suggestion = fuzzymatch(code, w.codes);
45+
w.unknown_code({ start, end }, code, suggestion);
46+
}
47+
} else if (w.codes.includes(replacement)) {
48+
ignores.push(replacement);
49+
}
50+
}
51+
52+
if (!match[2]) {
53+
break;
54+
}
55+
}
56+
57+
return ignores;
1758
}

packages/svelte/src/compiler/warnings.js

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,79 @@ function w(node, code, message) {
4040
});
4141
}
4242

43+
export const codes = [
44+
"a11y_accesskey",
45+
"a11y_aria_activedescendant_has_tabindex",
46+
"a11y_aria_attributes",
47+
"a11y_autocomplete_valid",
48+
"a11y_autofocus",
49+
"a11y_click_events_have_key_events",
50+
"a11y_distracting_elements",
51+
"a11y_figcaption_index",
52+
"a11y_figcaption_parent",
53+
"a11y_hidden",
54+
"a11y_img_redundant_alt",
55+
"a11y_incorrect_aria_attribute_type",
56+
"a11y_incorrect_aria_attribute_type_boolean",
57+
"a11y_incorrect_aria_attribute_type_id",
58+
"a11y_incorrect_aria_attribute_type_idlist",
59+
"a11y_incorrect_aria_attribute_type_integer",
60+
"a11y_incorrect_aria_attribute_type_token",
61+
"a11y_incorrect_aria_attribute_type_tokenlist",
62+
"a11y_incorrect_aria_attribute_type_tristate",
63+
"a11y_interactive_supports_focus",
64+
"a11y_invalid_attribute",
65+
"a11y_label_has_associated_control",
66+
"a11y_media_has_caption",
67+
"a11y_misplaced_role",
68+
"a11y_misplaced_scope",
69+
"a11y_missing_attribute",
70+
"a11y_missing_content",
71+
"a11y_mouse_events_have_key_events",
72+
"a11y_no_abstract_role",
73+
"a11y_no_interactive_element_to_noninteractive_role",
74+
"a11y_no_noninteractive_element_interactions",
75+
"a11y_no_noninteractive_element_to_interactive_role",
76+
"a11y_no_noninteractive_tabindex",
77+
"a11y_no_redundant_roles",
78+
"a11y_no_static_element_interactions",
79+
"a11y_positive_tabindex",
80+
"a11y_role_has_required_aria_props",
81+
"a11y_role_supports_aria_props",
82+
"a11y_role_supports_aria_props_implicit",
83+
"a11y_unknown_aria_attribute",
84+
"a11y_unknown_role",
85+
"legacy_code",
86+
"unknown_code",
87+
"options_deprecated_accessors",
88+
"options_deprecated_immutable",
89+
"options_missing_custom_element",
90+
"options_removed_enable_sourcemap",
91+
"options_removed_hydratable",
92+
"options_removed_loop_guard_timeout",
93+
"options_renamed_ssr_dom",
94+
"derived_iife",
95+
"export_let_unused",
96+
"non_reactive_update",
97+
"perf_avoid_inline_class",
98+
"perf_avoid_nested_class",
99+
"reactive_declaration_invalid_placement",
100+
"reactive_declaration_module_script",
101+
"state_referenced_locally",
102+
"store_rune_conflict",
103+
"css_unused_selector",
104+
"attribute_avoid_is",
105+
"attribute_global_event_reference",
106+
"attribute_illegal_colon",
107+
"attribute_invalid_property_name",
108+
"bind_invalid_each_rest",
109+
"block_empty",
110+
"component_name_lowercase",
111+
"element_invalid_self_closing_tag",
112+
"event_directive_deprecated",
113+
"slot_element_deprecated"
114+
];
115+
43116
/**
44117
* Avoid using accesskey
45118
* @param {null | NodeLike} node
@@ -414,6 +487,26 @@ export function a11y_unknown_role(node, role, suggestion) {
414487
w(node, "a11y_unknown_role", suggestion ? `Unknown role '${role}'. Did you mean '${suggestion}'?` : `Unknown role '${role}'`);
415488
}
416489

490+
/**
491+
* `%code%` is no longer valid — please use `%suggestion%` instead
492+
* @param {null | NodeLike} node
493+
* @param {string} code
494+
* @param {string} suggestion
495+
*/
496+
export function legacy_code(node, code, suggestion) {
497+
w(node, "legacy_code", `\`${code}\` is no longer valid — please use \`${suggestion}\` instead`);
498+
}
499+
500+
/**
501+
* `%code%` is not a recognised code (did you mean `%suggestion%`?)
502+
* @param {null | NodeLike} node
503+
* @param {string} code
504+
* @param {string | undefined | null} [suggestion]
505+
*/
506+
export function unknown_code(node, code, suggestion) {
507+
w(node, "unknown_code", suggestion ? `\`${code}\` is not a recognised code (did you mean \`${suggestion}\`?)` : `\`${code}\` is not a recognised code`);
508+
}
509+
417510
/**
418511
* The `accessors` option has been deprecated. It will have no effect in runes mode
419512
* @param {null | NodeLike} node
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<!-- svelte-ignore foo bar -->
1+
<!-- svelte-ignore foo, bar -->

packages/svelte/tests/parser-legacy/samples/comment-with-ignores/output.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22
"html": {
33
"type": "Fragment",
44
"start": 0,
5-
"end": 30,
5+
"end": 31,
66
"children": [
77
{
88
"type": "Comment",
99
"start": 0,
10-
"end": 30,
11-
"data": " svelte-ignore foo bar ",
10+
"end": 31,
11+
"data": " svelte-ignore foo, bar ",
1212
"ignores": ["foo", "bar"]
1313
}
1414
]
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<script>
2+
function foo() {
3+
// svelte-ignore non-top-level-reactive-declaration
4+
$: x = 1;
5+
}
6+
</script>
7+
8+
<!-- svelte-ignore a11y-missing-attribute -->
9+
<div>
10+
<img src="this-is-fine.jpg">
11+
</div>
12+
13+
<!-- svelte-ignore a11y-misplaced-scope -->
14+
<div scope></div>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[]

packages/svelte/tests/validator/samples/ignore-warning/input.svelte

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,7 @@
44
<marquee>but this is still discouraged</marquee>
55
</div>
66

7+
<!-- svelte-ignore a11y_misplaced_scope -->
8+
<div scope></div>
9+
710
<img src="potato.jpg">

packages/svelte/tests/validator/samples/ignore-warning/warnings.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@
1515
"code": "a11y_missing_attribute",
1616
"end": {
1717
"column": 22,
18-
"line": 7
18+
"line": 10
1919
},
2020
"message": "`<img>` element should have an alt attribute",
2121
"start": {
2222
"column": 0,
23-
"line": 7
23+
"line": 10
2424
}
2525
}
2626
]

packages/svelte/tests/validator/samples/ignore-warnings-cumulative/input.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<!-- svelte-ignore a11y_figcaption_parent a11y_missing_attribute -->
1+
<!-- svelte-ignore a11y_figcaption_parent, a11y_missing_attribute -->
22
<div>
33
<figure>
44
<img src="potato.jpg">

packages/svelte/tests/validator/samples/ignore-warnings-newline/input.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<!-- svelte-ignore a11y_missing_attribute
1+
<!-- svelte-ignore a11y_missing_attribute,
22
a11y_distracting_elements -->
33
<div>
44
<img src="this-is-fine.jpg">

packages/svelte/tests/validator/samples/ignore-warnings/input.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<!-- svelte-ignore a11y_missing_attribute a11y_distracting_elements -->
1+
<!-- svelte-ignore a11y_missing_attribute, a11y_distracting_elements -->
22
<div>
33
<img src="this-is-fine.jpg">
44
<marquee>but this is still discouraged</marquee>
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<svelte:options runes={true} />
2+
3+
<!-- svelte-ignore a11y-missing-attribute -->
4+
<div>
5+
<img src="this-is-fine.jpg">
6+
</div>
7+
8+
<!-- svelte-ignore ally_missing_attribute -->
9+
<div>
10+
<img src="this-is-fine.jpg">
11+
</div>
12+
13+
<!-- svelte-ignore a11y-misplaced-scope -->
14+
<div scope></div>
15+
16+
<!-- svelte-ignore a11y_misplaced_scope this is some prose -->
17+
<div scope></div>
18+
19+
<!-- svelte-ignore a11y_misplaced_scope this_is some-ambiguous prose -->
20+
<div scope></div>

0 commit comments

Comments
 (0)