mirror of https://github.com/sveltejs/svelte
commit
92250b59fa
@ -0,0 +1,5 @@
|
||||
---
|
||||
"svelte": patch
|
||||
---
|
||||
|
||||
breaking: disallow binding to component exports in runes mode
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
'svelte': patch
|
||||
---
|
||||
|
||||
fix: run render functions for dynamic void elements
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
'svelte': patch
|
||||
---
|
||||
|
||||
fix: allow events to continue propagating following an error
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
"svelte": patch
|
||||
---
|
||||
|
||||
fix: resolve type definition error in `svelte/compiler`
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
"svelte": patch
|
||||
---
|
||||
|
||||
feat: introduce types to express bindability
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
"svelte": patch
|
||||
---
|
||||
|
||||
fix: avoid hoisting error by using 'let' instead of 'var'
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
'svelte': patch
|
||||
---
|
||||
|
||||
fix: only destroy snippets when they have changed
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
'svelte': patch
|
||||
---
|
||||
|
||||
fix: add type arguments to Map and Set
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
'svelte': patch
|
||||
---
|
||||
|
||||
feat: implement `:global {...}` CSS blocks
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
"svelte": patch
|
||||
---
|
||||
|
||||
feat: add read-only `bind:focused`
|
@ -0,0 +1,59 @@
|
||||
## empty_attribute_shorthand
|
||||
|
||||
Attribute shorthand cannot be empty
|
||||
|
||||
## duplicate_attribute
|
||||
|
||||
Attributes need to be unique
|
||||
|
||||
## invalid_event_attribute_value
|
||||
|
||||
Event attribute must be a JavaScript expression, not a string
|
||||
|
||||
## invalid_attribute_name
|
||||
|
||||
'%name%' is not a valid attribute name
|
||||
|
||||
## animation_invalid_placement
|
||||
|
||||
An element that uses the `animate:` directive must be the only child of a keyed `{#each ...}` block
|
||||
|
||||
## animation_missing_key
|
||||
|
||||
An element that uses the `animate:` directive must be the only child of a keyed `{#each ...}` block. Did you forget to add a key to your each block?
|
||||
|
||||
## animation_duplicate
|
||||
|
||||
An element can only have one 'animate' directive
|
||||
|
||||
## invalid_event_modifier
|
||||
|
||||
Valid event modifiers are %list%
|
||||
|
||||
## invalid_component_event_modifier
|
||||
|
||||
Event modifiers other than 'once' can only be used on DOM elements
|
||||
|
||||
## invalid_event_modifier_combination
|
||||
|
||||
The '%modifier1%' and '%modifier2%' modifiers cannot be used together
|
||||
|
||||
## transition_duplicate
|
||||
|
||||
Cannot use multiple `%type%:` directives on a single element
|
||||
|
||||
## transition_conflict
|
||||
|
||||
Cannot use `%type%:` alongside existing `%existing%:` directive
|
||||
|
||||
## invalid_let_directive_placement
|
||||
|
||||
`let:` directive at invalid position
|
||||
|
||||
## invalid_style_directive_modifier
|
||||
|
||||
Invalid 'style:' modifier. Valid modifiers are: 'important'
|
||||
|
||||
## invalid_sequence_expression
|
||||
|
||||
Sequence expressions are not allowed as attribute/directive values in runes mode, unless wrapped in parentheses
|
@ -0,0 +1,35 @@
|
||||
## invalid_binding_expression
|
||||
|
||||
Can only bind to an Identifier or MemberExpression
|
||||
|
||||
## invalid_binding_value
|
||||
|
||||
Can only bind to state or props
|
||||
|
||||
## bind_invalid_target
|
||||
|
||||
`bind:%name%` can only be used with %elements%
|
||||
|
||||
## bind_invalid
|
||||
|
||||
`bind:%name%` is not a valid binding
|
||||
|
||||
## bind_invalid_detailed
|
||||
|
||||
`bind:%name%` is not a valid binding. %explanation%
|
||||
|
||||
## invalid_type_attribute
|
||||
|
||||
'type' attribute must be a static text value if input uses two-way binding
|
||||
|
||||
## invalid_multiple_attribute
|
||||
|
||||
'multiple' attribute must be static if select uses two-way binding
|
||||
|
||||
## missing_contenteditable_attribute
|
||||
|
||||
'contenteditable' attribute is required for textContent, innerHTML and innerText two-way bindings
|
||||
|
||||
## dynamic_contenteditable_attribute
|
||||
|
||||
'contenteditable' attribute cannot be dynamic if element uses two-way binding
|
@ -0,0 +1,7 @@
|
||||
## invalid_compiler_option
|
||||
|
||||
Invalid compiler option: %msg%
|
||||
|
||||
## removed_compiler_option
|
||||
|
||||
Invalid compiler option: %msg%
|
@ -0,0 +1,3 @@
|
||||
## invalid_component_directive
|
||||
|
||||
This type of directive is not valid on components
|
@ -0,0 +1,3 @@
|
||||
## invalid_const_placement
|
||||
|
||||
{@const} must be the immediate child of {#snippet}, {#if}, {:else if}, {:else}, {#each}, {:then}, {:catch}, <svelte:fragment> or <Component>
|
@ -0,0 +1,51 @@
|
||||
## invalid_css_empty_declaration
|
||||
|
||||
Declaration cannot be empty
|
||||
|
||||
## invalid_css_global_block_list
|
||||
|
||||
A :global {...} block cannot be part of a selector list with more than one item
|
||||
|
||||
## invalid_css_global_block_modifier
|
||||
|
||||
A :global {...} block cannot modify an existing selector
|
||||
|
||||
## invalid_css_global_block_combinator
|
||||
|
||||
A :global {...} block cannot follow a %name% combinator
|
||||
|
||||
## invalid_css_global_block_declaration
|
||||
|
||||
A :global {...} block can only contain rules, not declarations
|
||||
|
||||
## invalid_css_global_placement
|
||||
|
||||
:global(...) can be at the start or end of a selector sequence, but not in the middle
|
||||
|
||||
## invalid_css_global_selector
|
||||
|
||||
:global(...) must contain exactly one selector
|
||||
|
||||
## invalid_css_global_selector_list
|
||||
|
||||
:global(...) must not contain type or universal selectors when used in a compound selector
|
||||
|
||||
## invalid_css_type_selector_placement
|
||||
|
||||
:global(...) must not be followed with a type selector
|
||||
|
||||
## invalid_css_selector
|
||||
|
||||
Invalid selector
|
||||
|
||||
## invalid_css_identifier
|
||||
|
||||
Expected a valid CSS identifier
|
||||
|
||||
## invalid_nesting_selector
|
||||
|
||||
Nesting selectors can only be used inside a rule
|
||||
|
||||
## invalid_css_declaration
|
||||
|
||||
Declaration cannot be empty
|
@ -0,0 +1,27 @@
|
||||
## invalid_textarea_content
|
||||
|
||||
A `<textarea>` can have either a value attribute or (equivalently) child content, but not both
|
||||
|
||||
## invalid_void_content
|
||||
|
||||
Void elements cannot have children or closing tags
|
||||
|
||||
## invalid_element_content
|
||||
|
||||
<%name%> cannot have children
|
||||
|
||||
## invalid_tag_name
|
||||
|
||||
Expected valid tag name
|
||||
|
||||
## invalid_node_placement
|
||||
|
||||
%thing% is invalid inside <%parent%>
|
||||
|
||||
## illegal_title_attribute
|
||||
|
||||
`<title>` cannot have attributes nor directives
|
||||
|
||||
## invalid_title_content
|
||||
|
||||
`<title>` can only contain text and {tags}
|
@ -0,0 +1,3 @@
|
||||
## cyclical_reactive_declaration
|
||||
|
||||
Cyclical dependency detected: %cycle%
|
@ -0,0 +1,147 @@
|
||||
## unclosed_element
|
||||
|
||||
`<%name%>` was left open
|
||||
|
||||
## unclosed_block
|
||||
|
||||
Block was left open
|
||||
|
||||
## unexpected_block_close
|
||||
|
||||
Unexpected block closing tag
|
||||
|
||||
## unexpected_eof
|
||||
|
||||
Unexpected end of input
|
||||
|
||||
## js_parse_error
|
||||
|
||||
%message%
|
||||
|
||||
## expected_token
|
||||
|
||||
Expected token %token%
|
||||
|
||||
## unexpected_reserved_word
|
||||
|
||||
'%word%' is a reserved word in JavaScript and cannot be used here
|
||||
|
||||
## missing_whitespace
|
||||
|
||||
Expected whitespace
|
||||
|
||||
## expected_pattern
|
||||
|
||||
Expected identifier or destructure pattern
|
||||
|
||||
## invalid_script_context
|
||||
|
||||
If the context attribute is supplied, its value must be "module"
|
||||
|
||||
## invalid_elseif
|
||||
|
||||
'elseif' should be 'else if'
|
||||
|
||||
## invalid_continuing_block_placement
|
||||
|
||||
{:...} block is invalid at this position (did you forget to close the preceeding element or block?)
|
||||
|
||||
## invalid_block_missing_parent
|
||||
|
||||
%child% block must be a child of %parent%
|
||||
|
||||
## duplicate_block_part
|
||||
|
||||
%name% cannot appear more than once within a block
|
||||
|
||||
## expected_block_type
|
||||
|
||||
Expected 'if', 'each', 'await', 'key' or 'snippet'
|
||||
|
||||
## expected_identifier
|
||||
|
||||
Expected an identifier
|
||||
|
||||
## invalid_debug
|
||||
|
||||
{@debug ...} arguments must be identifiers, not arbitrary expressions
|
||||
|
||||
## invalid_const
|
||||
|
||||
{@const ...} must be an assignment
|
||||
|
||||
## invalid_block_placement
|
||||
|
||||
{#%name% ...} block cannot be %location%
|
||||
|
||||
## invalid_tag_placement
|
||||
|
||||
{@%name% ...} tag cannot be %location%
|
||||
|
||||
## missing_attribute_value
|
||||
|
||||
Expected attribute value
|
||||
|
||||
## unclosed_attribute_value
|
||||
|
||||
Expected closing %delimiter% character
|
||||
|
||||
## invalid_directive_value
|
||||
|
||||
Directive value must be a JavaScript expression enclosed in curly braces
|
||||
|
||||
## empty_directive_name
|
||||
|
||||
%type% name cannot be empty
|
||||
|
||||
## invalid_closing_tag
|
||||
|
||||
</%name%> attempted to close an element that was not open
|
||||
|
||||
## invalid_closing_tag_after_autoclose
|
||||
|
||||
</%name%> attempted to close element that was already automatically closed by <%reason%> (cannot nest <%reason%> inside <%name%>)
|
||||
|
||||
## invalid_dollar_binding
|
||||
|
||||
The $ name is reserved, and cannot be used for variables and imports
|
||||
|
||||
## invalid_dollar_prefix
|
||||
|
||||
The $ prefix is reserved, and cannot be used for variables and imports
|
||||
|
||||
## invalid_dollar_global
|
||||
|
||||
The $ name is reserved. To reference a global variable called $, use globalThis.$
|
||||
|
||||
## illegal_subscription
|
||||
|
||||
Cannot reference store value inside `<script context="module">`
|
||||
|
||||
## duplicate_style_element
|
||||
|
||||
A component can have a single top-level `<style>` element
|
||||
|
||||
## duplicate_script_element
|
||||
|
||||
A component can have a single top-level `<script>` element and/or a single top-level `<script context="module">` element
|
||||
|
||||
## invalid_render_expression
|
||||
|
||||
{@render ...} tags can only contain call expressions
|
||||
|
||||
## invalid_render_arguments
|
||||
|
||||
expected at most one argument
|
||||
|
||||
## invalid_render_call
|
||||
|
||||
Calling a snippet function using apply, bind or call is not allowed
|
||||
|
||||
## invalid_render_spread_argument
|
||||
|
||||
cannot use spread arguments in {@render ...} tags
|
||||
|
||||
## invalid_snippet_rest_parameter
|
||||
|
||||
snippets do not support rest parameters; use an array instead
|
@ -0,0 +1,95 @@
|
||||
## invalid_legacy_props
|
||||
|
||||
Cannot use `$$props` in runes mode
|
||||
|
||||
## invalid_legacy_rest_props
|
||||
|
||||
Cannot use `$$restProps` in runes mode
|
||||
|
||||
## invalid_legacy_reactive_statement
|
||||
|
||||
`$:` is not allowed in runes mode, use `$derived` or `$effect` instead
|
||||
|
||||
## invalid_legacy_export
|
||||
|
||||
Cannot use `export let` in runes mode — use $props instead
|
||||
|
||||
## invalid_rune_usage
|
||||
|
||||
Cannot use %rune% rune in non-runes mode
|
||||
|
||||
## invalid_state_export
|
||||
|
||||
Cannot export state from a module if it is reassigned. Either export a function returning the state value or only mutate the state value's properties
|
||||
|
||||
## invalid_derived_export
|
||||
|
||||
Cannot export derived state from a module. To expose the current derived value, export a function returning its value
|
||||
|
||||
## invalid_props_id
|
||||
|
||||
`$props()` can only be used with an object destructuring pattern
|
||||
|
||||
## invalid_props_pattern
|
||||
|
||||
`$props()` assignment must not contain nested properties or computed keys
|
||||
|
||||
## invalid_props_location
|
||||
|
||||
`$props()` can only be used at the top level of components as a variable declaration initializer
|
||||
|
||||
## invalid_bindable_location
|
||||
|
||||
`$bindable()` can only be used inside a `$props()` declaration
|
||||
|
||||
## invalid_state_location
|
||||
|
||||
`%rune%(...)` can only be used as a variable declaration initializer or a class field
|
||||
|
||||
## invalid_effect_location
|
||||
|
||||
`$effect()` can only be used as an expression statement
|
||||
|
||||
## invalid_host_location
|
||||
|
||||
`$host()` can only be used inside custom element component instances
|
||||
|
||||
## invalid_assignment
|
||||
|
||||
Cannot assign to %thing%
|
||||
|
||||
## invalid_binding
|
||||
|
||||
Cannot bind to %thing%
|
||||
|
||||
## invalid_rune_args
|
||||
|
||||
`%rune%` cannot be called with arguments
|
||||
|
||||
## invalid_rune_args_length
|
||||
|
||||
`%rune%` must be called with %args%
|
||||
|
||||
## invalid_runes_mode_import
|
||||
|
||||
%name% cannot be used in runes mode
|
||||
|
||||
## duplicate_props_rune
|
||||
|
||||
Cannot use `$props()` more than once
|
||||
|
||||
## invalid_each_assignment
|
||||
|
||||
Cannot reassign or bind to each block argument in runes mode. Use the array and index variables instead (e.g. `array[i] = value` instead of `entry = value`)
|
||||
|
||||
## invalid_snippet_assignment
|
||||
|
||||
Cannot reassign or bind to snippet parameter
|
||||
|
||||
## invalid_derived_call
|
||||
|
||||
`$derived.call(...)` has been replaced with `$derived.by(...)`
|
||||
|
||||
## conflicting_property_name
|
||||
|
||||
Cannot have a property and a component export with the same name
|
@ -0,0 +1,31 @@
|
||||
## invalid_slot_element_attribute
|
||||
|
||||
`<slot>` can only receive attributes and (optionally) let directives
|
||||
|
||||
## invalid_slot_attribute
|
||||
|
||||
slot attribute must be a static value
|
||||
|
||||
## invalid_slot_name_default
|
||||
|
||||
`default` is a reserved word — it cannot be used as a slot name
|
||||
|
||||
## invalid_slot_name
|
||||
|
||||
slot attribute must be a static value
|
||||
|
||||
## invalid_slot_placement
|
||||
|
||||
Element with a slot='...' attribute must be a child of a component or a descendant of a custom element
|
||||
|
||||
## duplicate_slot_name
|
||||
|
||||
Duplicate slot name '%name%' in <%component%>
|
||||
|
||||
## invalid_default_slot_content
|
||||
|
||||
Found default slot content alongside an explicit slot="default"
|
||||
|
||||
## conflicting_children_snippet
|
||||
|
||||
Cannot use explicit children snippet at the same time as implicit children content. Remove either the non-whitespace content or the children snippet block
|
@ -0,0 +1,99 @@
|
||||
## invalid_svelte_option_attribute
|
||||
|
||||
`<svelte:options>` can only receive static attributes
|
||||
|
||||
## invalid_svelte_option_namespace
|
||||
|
||||
Unsupported `<svelte:option>` value for "namespace". Valid values are "html", "svg" or "foreign"
|
||||
|
||||
## tag_option_deprecated
|
||||
|
||||
"tag" option is deprecated — use "customElement" instead
|
||||
|
||||
## invalid_svelte_option_runes
|
||||
|
||||
Unsupported `<svelte:option>` value for "runes". Valid values are true or false
|
||||
|
||||
## invalid_svelte_option_accessors
|
||||
|
||||
Unsupported `<svelte:option>` value for "accessors". Valid values are true or false
|
||||
|
||||
## invalid_svelte_option_preserveWhitespace
|
||||
|
||||
Unsupported `<svelte:option>` value for "preserveWhitespace". Valid values are true or false
|
||||
|
||||
## invalid_svelte_option_immutable
|
||||
|
||||
Unsupported `<svelte:option>` value for "immutable". Valid values are true or false
|
||||
|
||||
## invalid_tag_property
|
||||
|
||||
Tag name must be two or more words joined by the "-" character
|
||||
|
||||
## invalid_svelte_option_customElement
|
||||
|
||||
"customElement" must be a string literal defining a valid custom element name or an object of the form { tag: string; shadow?: "open" | "none"; props?: { [key: string]: { attribute?: string; reflect?: boolean; type: .. } } }
|
||||
|
||||
## invalid_customElement_props_attribute
|
||||
|
||||
"props" must be a statically analyzable object literal of the form "{ [key: string]: { attribute?: string; reflect?: boolean; type?: "String" | "Boolean" | "Number" | "Array" | "Object" }"
|
||||
|
||||
## invalid_customElement_shadow_attribute
|
||||
|
||||
"shadow" must be either "open" or "none"
|
||||
|
||||
## unknown_svelte_option_attribute
|
||||
|
||||
`<svelte:options>` unknown attribute '%name%'
|
||||
|
||||
## illegal_svelte_head_attribute
|
||||
|
||||
`<svelte:head>` cannot have attributes nor directives
|
||||
|
||||
## invalid_svelte_fragment_attribute
|
||||
|
||||
`<svelte:fragment>` can only have a slot attribute and (optionally) a let: directive
|
||||
|
||||
## invalid_svelte_fragment_slot
|
||||
|
||||
`<svelte:fragment>` slot attribute must have a static value
|
||||
|
||||
## invalid_svelte_fragment_placement
|
||||
|
||||
`<svelte:fragment>` must be the direct child of a component
|
||||
|
||||
## invalid_svelte_element_placement
|
||||
|
||||
<%name%> tags cannot be inside elements or blocks
|
||||
|
||||
## duplicate_svelte_element
|
||||
|
||||
A component can only have one <%name%> element
|
||||
|
||||
## invalid_self_placement
|
||||
|
||||
`<svelte:self>` components can only exist inside {#if} blocks, {#each} blocks, {#snippet} blocks or slots passed to components
|
||||
|
||||
## missing_svelte_element_definition
|
||||
|
||||
`<svelte:element>` must have a 'this' attribute
|
||||
|
||||
## missing_svelte_component_definition
|
||||
|
||||
`<svelte:component>` must have a 'this' attribute
|
||||
|
||||
## invalid_svelte_element_definition
|
||||
|
||||
Invalid element definition — must be an {expression}
|
||||
|
||||
## invalid_svelte_component_definition
|
||||
|
||||
Invalid component definition — must be an {expression}
|
||||
|
||||
## invalid_svelte_tag
|
||||
|
||||
Valid `<svelte:...>` tag names are %list%
|
||||
|
||||
## conflicting_slot_usage
|
||||
|
||||
Cannot use `<slot>` syntax and `{@render ...}` tags in the same component. Migrate towards `{@render ...}` tags completely.
|
@ -0,0 +1,19 @@
|
||||
## illegal_global
|
||||
|
||||
`%name%` is an illegal variable name. To reference a global variable called `%name%`, use `globalThis.%name%`
|
||||
|
||||
## duplicate_declaration
|
||||
|
||||
`%name%` has already been declared
|
||||
|
||||
## default_export
|
||||
|
||||
A component cannot have a default export
|
||||
|
||||
## illegal_variable_declaration
|
||||
|
||||
Cannot declare same variable name which is imported inside `<script context="module">`
|
||||
|
||||
## illegal_store_subscription
|
||||
|
||||
Cannot subscribe to stores that are not declared at the top level of the component
|
@ -0,0 +1,171 @@
|
||||
## a11y_aria_attributes
|
||||
|
||||
<%name%> should not have aria-* attributes
|
||||
|
||||
## a11y_unknown_aria_attribute
|
||||
|
||||
Unknown aria attribute 'aria-%attribute%'
|
||||
|
||||
## a11y_unknown_aria_attribute_suggestion
|
||||
|
||||
Unknown aria attribute 'aria-%attribute%'. Did you mean '%suggestion%'?
|
||||
|
||||
## a11y_hidden
|
||||
|
||||
<%name%> element should not be hidden
|
||||
|
||||
## a11y_incorrect_aria_attribute_type_boolean
|
||||
|
||||
The value of '%attribute%' must be either 'true' or 'false'
|
||||
|
||||
## a11y_incorrect_aria_attribute_type_integer
|
||||
|
||||
The value of '%attribute%' must be an integer
|
||||
|
||||
## a11y_incorrect_aria_attribute_type_id
|
||||
|
||||
The value of '%attribute%' must be a string that represents a DOM element ID
|
||||
|
||||
## a11y_incorrect_aria_attribute_type_idlist
|
||||
|
||||
The value of '%attribute%' must be a space-separated list of strings that represent DOM element IDs
|
||||
|
||||
## a11y_incorrect_aria_attribute_type_tristate
|
||||
|
||||
The value of '%attribute%' must be exactly one of true, false, or mixed
|
||||
|
||||
## a11y_incorrect_aria_attribute_type_token
|
||||
|
||||
The value of '%attribute%' must be exactly one of %values%
|
||||
|
||||
## a11y_incorrect_aria_attribute_type_tokenlist
|
||||
|
||||
The value of '%attribute%' must be a space-separated list of one or more of %values%
|
||||
|
||||
## a11y_incorrect_aria_attribute_type
|
||||
|
||||
The value of '%attribute%' must be of type %type%
|
||||
|
||||
## a11y_aria_activedescendant_has_tabindex
|
||||
|
||||
Elements with attribute aria-activedescendant should have tabindex value
|
||||
|
||||
## a11y_misplaced_role
|
||||
|
||||
<%name%> should not have role attribute
|
||||
|
||||
## a11y_no_abstract_role
|
||||
|
||||
Abstract role '%role%' is forbidden
|
||||
|
||||
## a11y_unknown_role
|
||||
|
||||
Unknown role '%role%'
|
||||
|
||||
## a11y_unknown_role_suggestion
|
||||
|
||||
Unknown role '%role%'. Did you mean '%suggestion%'?
|
||||
|
||||
## a11y_no_redundant_roles
|
||||
|
||||
Redundant role '%role%'
|
||||
|
||||
## a11y_role_has_required_aria_props
|
||||
|
||||
Elements with the ARIA role "%role%" must have the following attributes defined: %props%
|
||||
|
||||
## a11y_interactive_supports_focus
|
||||
|
||||
Elements with the '%role%' interactive role must have a tabindex value.
|
||||
|
||||
## a11y_no_interactive_element_to_noninteractive_role
|
||||
|
||||
<%element%> cannot have role '%role%'
|
||||
|
||||
## a11y_no_noninteractive_element_to_interactive_role
|
||||
|
||||
Non-interactive element <%element%> cannot have interactive role '%role%'
|
||||
|
||||
## a11y_accesskey
|
||||
|
||||
Avoid using accesskey
|
||||
|
||||
## a11y_autofocus
|
||||
|
||||
Avoid using autofocus
|
||||
|
||||
## a11y_misplaced_scope
|
||||
|
||||
The scope attribute should only be used with <th> elements
|
||||
|
||||
## a11y_positive_tabindex
|
||||
|
||||
Avoid tabindex values above zero
|
||||
|
||||
## a11y_click_events_have_key_events
|
||||
|
||||
Visible, non-interactive elements with a click event must be accompanied by a keyboard event handler. Consider whether an interactive element such as <button type="button"> or <a> might be more appropriate. See https://svelte.dev/docs/accessibility-warnings#a11y-click-events-have-key-events for more details.
|
||||
|
||||
## a11y_no_noninteractive_tabindex
|
||||
|
||||
noninteractive element cannot have nonnegative tabIndex value
|
||||
|
||||
## a11y_role_supports_aria_props
|
||||
|
||||
The attribute '%attribute%' is not supported by the role '%role%'
|
||||
|
||||
## a11y_role_supports_aria_props_implicit
|
||||
|
||||
The attribute '%attribute%' is not supported by the role '%role%'. This role is implicit on the element <%name%>
|
||||
|
||||
## a11y_no_noninteractive_element_interactions
|
||||
|
||||
Non-interactive element <%element%> should not be assigned mouse or keyboard event listeners.
|
||||
|
||||
## a11y_no_static_element_interactions
|
||||
|
||||
<%element%> with a %handler% handler must have an ARIA role
|
||||
|
||||
## a11y_invalid_attribute
|
||||
|
||||
'%href_value%' is not a valid %href_attribute% attribute
|
||||
|
||||
## a11y_missing_attribute
|
||||
|
||||
<%name%> element should have %article% %sequence% attribute
|
||||
|
||||
## a11y_autocomplete_valid
|
||||
|
||||
The value '%value%' is not supported by the attribute 'autocomplete' on element <input type="%type%">
|
||||
|
||||
## a11y_img_redundant_alt
|
||||
|
||||
Screenreaders already announce <img> elements as an image.
|
||||
|
||||
## a11y_label_has_associated_control
|
||||
|
||||
A form label must be associated with a control.
|
||||
|
||||
## a11y_media_has_caption
|
||||
|
||||
<video> elements must have a <track kind="captions">
|
||||
|
||||
## a11y_distracting_elements
|
||||
|
||||
Avoid <%name%> elements
|
||||
|
||||
## a11y_figcaption_parent
|
||||
|
||||
`<figcaption>` must be an immediate child of `<figure>`
|
||||
|
||||
## a11y_figcaption_index
|
||||
|
||||
`<figcaption>` must be first or last child of `<figure>`
|
||||
|
||||
## a11y_mouse_events_have_key_events
|
||||
|
||||
'%event%' event must be accompanied by '%accompanied_by%' event
|
||||
|
||||
## a11y_missing_content
|
||||
|
||||
<%name%> element should have child content
|
@ -0,0 +1,15 @@
|
||||
## avoid_is
|
||||
|
||||
The "is" attribute is not supported cross-browser and should be avoided
|
||||
|
||||
## global_event_reference
|
||||
|
||||
You are referencing globalThis.%name%. Did you forget to declare a variable with that name?
|
||||
|
||||
## illegal_attribute_character
|
||||
|
||||
Attributes should not contain ':' characters to prevent ambiguity with Svelte directives
|
||||
|
||||
## invalid_html_attribute
|
||||
|
||||
'%wrong%' is not a valid HTML attribute. Did you mean '%right%'?
|
@ -0,0 +1,3 @@
|
||||
## empty_block
|
||||
|
||||
Empty block
|
@ -0,0 +1,3 @@
|
||||
## component_name_lowercase
|
||||
|
||||
<%name%> will be treated as an HTML element unless it begins with a capital letter
|
@ -0,0 +1,3 @@
|
||||
## css_unused_selector
|
||||
|
||||
Unused CSS selector "%name%"
|
@ -0,0 +1,27 @@
|
||||
## no_reactive_declaration
|
||||
|
||||
Reactive declarations only exist at the top level of the instance script
|
||||
|
||||
## module_script_reactive_declaration
|
||||
|
||||
All dependencies of the reactive declaration are declared in a module script and will not be reactive
|
||||
|
||||
## unused_export_let
|
||||
|
||||
Component has unused export property '%name%'. If it is for external reference only, please consider using `export const %name%`
|
||||
|
||||
## deprecated_slot_element
|
||||
|
||||
Using <slot> to render parent content is deprecated. Use {@render ...} tags instead.
|
||||
|
||||
## deprecated_event_handler
|
||||
|
||||
Using on:%name% to listen to the %name% event is is deprecated. Use the event attribute on%name% instead.
|
||||
|
||||
## deprecated_accessors
|
||||
|
||||
The accessors option has been deprecated. It will have no effect in runes mode.
|
||||
|
||||
## deprecated_immutable
|
||||
|
||||
The immutable option has been deprecated. It will have no effect in runes mode.
|
@ -0,0 +1,3 @@
|
||||
## invalid_self_closing_tag
|
||||
|
||||
Self-closing HTML tags for non-void elements are ambiguous — use <%name% ...></%name%> rather than <%name% ... />
|
@ -0,0 +1,3 @@
|
||||
## missing_custom_element_compile_option
|
||||
|
||||
The 'customElement' option is used when generating a custom element. Did you forget the 'customElement: true' compile option?
|
@ -0,0 +1,7 @@
|
||||
## avoid_inline_class
|
||||
|
||||
Avoid 'new class' — instead, declare the class at the top level scope
|
||||
|
||||
## avoid_nested_class
|
||||
|
||||
Avoid declaring classes below the top level scope
|
@ -0,0 +1,19 @@
|
||||
## store_with_rune_name
|
||||
|
||||
It looks like you're using the `$%name%` rune, but there is a local binding called `%name%`. Referencing a local variable with a `$` prefix will create a store subscription. Please rename `%name%` to avoid the ambiguity
|
||||
|
||||
## non_state_reference
|
||||
|
||||
`%name%` is updated, but is not declared with `$state(...)`. Changing its value will not correctly trigger updates
|
||||
|
||||
## derived_iife
|
||||
|
||||
Use `$derived.by(() => {...})` instead of `$derived((() => {...})())`
|
||||
|
||||
## invalid_props_declaration
|
||||
|
||||
Component properties are declared using `$props()` in runes mode. Did you forget to call the function?
|
||||
|
||||
## invalid_bindable_declaration
|
||||
|
||||
Bindable component properties are declared using `$bindable()` in runes mode. Did you forget to call the function?
|
@ -0,0 +1,7 @@
|
||||
## static_state_reference
|
||||
|
||||
State referenced in its own scope will never update. Did you mean to reference it inside a closure?
|
||||
|
||||
## invalid_rest_eachblock_binding
|
||||
|
||||
The rest operator (...) will create a new object and binding '%name%' with the original object will not work
|
@ -0,0 +1,223 @@
|
||||
// @ts-check
|
||||
import fs from 'node:fs';
|
||||
import * as acorn from 'acorn';
|
||||
import { walk } from 'zimmerframe';
|
||||
import * as esrap from 'esrap';
|
||||
|
||||
const messages = {};
|
||||
const seen = new Set();
|
||||
|
||||
for (const category of fs.readdirSync('messages')) {
|
||||
messages[category] = {};
|
||||
|
||||
for (const file of fs.readdirSync(`messages/${category}`)) {
|
||||
if (!file.endsWith('.md')) continue;
|
||||
|
||||
const markdown = fs.readFileSync(`messages/${category}/${file}`, 'utf-8');
|
||||
|
||||
for (const match of markdown.matchAll(/## ([\w]+)\n\n([^]+?)(?=$|\n\n## )/g)) {
|
||||
const [_, code, text] = match;
|
||||
|
||||
if (seen.has(code)) {
|
||||
throw new Error(`Duplicate message code ${category}/${code}`);
|
||||
}
|
||||
|
||||
seen.add(code);
|
||||
messages[category][code] = text.trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function transform(name, dest) {
|
||||
const source = fs.readFileSync(new URL(`./templates/${name}.js`, import.meta.url), 'utf-8');
|
||||
|
||||
const comments = [];
|
||||
|
||||
const ast = acorn.parse(source, {
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
onComment: (block, value, start, end) => {
|
||||
if (block && /\n/.test(value)) {
|
||||
let a = start;
|
||||
while (a > 0 && source[a - 1] !== '\n') a -= 1;
|
||||
|
||||
let b = a;
|
||||
while (/[ \t]/.test(source[b])) b += 1;
|
||||
|
||||
const indentation = source.slice(a, b);
|
||||
value = value.replace(new RegExp(`^${indentation}`, 'gm'), '');
|
||||
}
|
||||
|
||||
comments.push({ type: block ? 'Block' : 'Line', value, start, end });
|
||||
}
|
||||
});
|
||||
|
||||
walk(ast, null, {
|
||||
_(node, { next }) {
|
||||
let comment;
|
||||
|
||||
while (comments[0] && comments[0].start < node.start) {
|
||||
comment = comments.shift();
|
||||
// @ts-expect-error
|
||||
(node.leadingComments ||= []).push(comment);
|
||||
}
|
||||
|
||||
next();
|
||||
|
||||
if (comments[0]) {
|
||||
const slice = source.slice(node.end, comments[0].start);
|
||||
|
||||
if (/^[,) \t]*$/.test(slice)) {
|
||||
// @ts-expect-error
|
||||
node.trailingComments = [comments.shift()];
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const category = messages[name];
|
||||
|
||||
// find the `export function CODE` node
|
||||
const index = ast.body.findIndex((node) => {
|
||||
if (
|
||||
node.type === 'ExportNamedDeclaration' &&
|
||||
node.declaration &&
|
||||
node.declaration.type === 'FunctionDeclaration'
|
||||
) {
|
||||
return node.declaration.id.name === 'CODE';
|
||||
}
|
||||
});
|
||||
|
||||
if (index === -1) throw new Error(`missing export function CODE in ${name}.js`);
|
||||
|
||||
const template_node = ast.body[index];
|
||||
ast.body.splice(index, 1);
|
||||
|
||||
for (const code in category) {
|
||||
const message = category[code];
|
||||
const vars = [];
|
||||
for (const match of message.matchAll(/%(\w+)%/g)) {
|
||||
const name = match[1];
|
||||
if (!vars.includes(name)) {
|
||||
vars.push(match[1]);
|
||||
}
|
||||
}
|
||||
|
||||
const clone = walk(/** @type {import('estree').Node} */ (template_node), null, {
|
||||
// @ts-expect-error Block is a block comment, which is not recognised
|
||||
Block(node, context) {
|
||||
if (!node.value.includes('PARAMETER')) return;
|
||||
|
||||
const value = node.value
|
||||
.split('\n')
|
||||
.map((line) => {
|
||||
if (line === ' * MESSAGE') {
|
||||
return message
|
||||
.split('\n')
|
||||
.map((line) => ` * ${line}`)
|
||||
.join('\n');
|
||||
}
|
||||
|
||||
if (line.includes('PARAMETER')) {
|
||||
return vars.map((name) => ` * @param {string} ${name}`).join('\n');
|
||||
}
|
||||
|
||||
return line;
|
||||
})
|
||||
.filter((x) => x !== '')
|
||||
.join('\n');
|
||||
|
||||
if (value !== node.value) {
|
||||
return { ...node, value };
|
||||
}
|
||||
},
|
||||
FunctionDeclaration(node, context) {
|
||||
if (node.id.name !== 'CODE') return;
|
||||
|
||||
const params = [];
|
||||
|
||||
for (const param of node.params) {
|
||||
if (param.type === 'Identifier' && param.name === 'PARAMETER') {
|
||||
params.push(...vars.map((name) => ({ type: 'Identifier', name })));
|
||||
} else {
|
||||
params.push(param);
|
||||
}
|
||||
}
|
||||
|
||||
return /** @type {import('estree').FunctionDeclaration} */ ({
|
||||
.../** @type {import('estree').FunctionDeclaration} */ (context.next()),
|
||||
params,
|
||||
id: {
|
||||
...node.id,
|
||||
name: code
|
||||
}
|
||||
});
|
||||
},
|
||||
Literal(node) {
|
||||
if (node.value === 'CODE') {
|
||||
return {
|
||||
type: 'Literal',
|
||||
value: code
|
||||
};
|
||||
}
|
||||
},
|
||||
Identifier(node) {
|
||||
if (node.name !== 'MESSAGE') return;
|
||||
|
||||
if (/%\w+%/.test(message)) {
|
||||
const parts = message.split(/(%\w+%)/);
|
||||
|
||||
/** @type {import('estree').Expression[]} */
|
||||
const expressions = [];
|
||||
|
||||
/** @type {import('estree').TemplateElement[]} */
|
||||
const quasis = [];
|
||||
|
||||
for (let i = 0; i < parts.length; i += 1) {
|
||||
const part = parts[i];
|
||||
if (i % 2 === 0) {
|
||||
const str = part.replace(/(`|\${)/g, '\\$1');
|
||||
quasis.push({
|
||||
type: 'TemplateElement',
|
||||
value: { raw: str, cooked: str },
|
||||
tail: i === parts.length - 1
|
||||
});
|
||||
} else {
|
||||
expressions.push({
|
||||
type: 'Identifier',
|
||||
name: part.slice(1, -1)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'TemplateLiteral',
|
||||
expressions,
|
||||
quasis
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'Literal',
|
||||
value: message
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// @ts-expect-error
|
||||
ast.body.push(clone);
|
||||
}
|
||||
|
||||
// @ts-expect-error
|
||||
const module = esrap.print(ast);
|
||||
|
||||
fs.writeFileSync(
|
||||
dest,
|
||||
`/* This file is generated by scripts/process-messages/index.js. Do not edit! */\n\n` +
|
||||
module.code,
|
||||
'utf-8'
|
||||
);
|
||||
}
|
||||
|
||||
transform('compile-errors', 'src/compiler/errors.js');
|
||||
transform('compile-warnings', 'src/compiler/warnings.js');
|
@ -0,0 +1,74 @@
|
||||
/** @typedef {{ start?: number, end?: number }} NodeLike */
|
||||
|
||||
// interface is duplicated between here (used internally) and ./interfaces.js
|
||||
// (exposed publicly), and I'm not sure how to avoid that
|
||||
export class CompileError extends Error {
|
||||
name = 'CompileError';
|
||||
|
||||
/** @type {import('#compiler').CompileError['filename']} */
|
||||
filename = undefined;
|
||||
|
||||
/** @type {import('#compiler').CompileError['position']} */
|
||||
position = undefined;
|
||||
|
||||
/** @type {import('#compiler').CompileError['start']} */
|
||||
start = undefined;
|
||||
|
||||
/** @type {import('#compiler').CompileError['end']} */
|
||||
end = undefined;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} code
|
||||
* @param {string} message
|
||||
* @param {[number, number] | undefined} position
|
||||
*/
|
||||
constructor(code, message, position) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
toString() {
|
||||
let out = `${this.name}: ${this.message}`;
|
||||
|
||||
out += `\n(${this.code})`;
|
||||
|
||||
if (this.filename) {
|
||||
out += `\n${this.filename}`;
|
||||
|
||||
if (this.start) {
|
||||
out += `${this.start.line}:${this.start.column}`;
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {null | number | NodeLike} node
|
||||
* @param {string} code
|
||||
* @param {string} message
|
||||
* @returns {never}
|
||||
*/
|
||||
function e(node, code, message) {
|
||||
const start = typeof node === 'number' ? node : node?.start;
|
||||
const end = typeof node === 'number' ? node : node?.end;
|
||||
|
||||
throw new CompileError(
|
||||
code,
|
||||
message,
|
||||
start !== undefined && end !== undefined ? [start, end] : undefined
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* MESSAGE
|
||||
* @param {null | number | NodeLike} node
|
||||
* @param {string} PARAMETER
|
||||
* @returns {never}
|
||||
*/
|
||||
export function CODE(node, PARAMETER) {
|
||||
e(node, 'CODE', MESSAGE);
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
import { getLocator } from 'locate-character';
|
||||
|
||||
/** @typedef {{ start?: number, end?: number }} NodeLike */
|
||||
|
||||
/** @type {import('#compiler').Warning[]} */
|
||||
let warnings = [];
|
||||
|
||||
/** @type {string | undefined} */
|
||||
let filename;
|
||||
|
||||
let locator = getLocator('', { offsetLine: 1 });
|
||||
|
||||
/**
|
||||
* @param {{
|
||||
* source: string;
|
||||
* filename: string | undefined;
|
||||
* }} options
|
||||
* @returns {import('#compiler').Warning[]}
|
||||
*/
|
||||
export function reset_warnings(options) {
|
||||
filename = options.filename;
|
||||
locator = getLocator(options.source, { offsetLine: 1 });
|
||||
|
||||
return (warnings = []);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {null | NodeLike} node
|
||||
* @param {string} code
|
||||
* @param {string} message
|
||||
*/
|
||||
function w(node, code, message) {
|
||||
// @ts-expect-error
|
||||
if (node.ignores?.has(code)) return;
|
||||
|
||||
warnings.push({
|
||||
code,
|
||||
message,
|
||||
filename,
|
||||
start: node?.start !== undefined ? locator(node.start) : undefined,
|
||||
end: node?.end !== undefined ? locator(node.end) : undefined
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* MESSAGE
|
||||
* @param {null | NodeLike} node
|
||||
* @param {string} PARAMETER
|
||||
*/
|
||||
export function CODE(node, PARAMETER) {
|
||||
w(node, 'CODE', MESSAGE);
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* @param {string[]} strings
|
||||
* @param {string} conjunction
|
||||
*/
|
||||
export function list(strings, conjunction = 'or') {
|
||||
if (strings.length === 1) return strings[0];
|
||||
if (strings.length === 2) return `${strings[0]} ${conjunction} ${strings[1]}`;
|
||||
return `${strings.slice(0, -1).join(', ')} ${conjunction} ${strings[strings.length - 1]}`;
|
||||
}
|
@ -1,340 +1,41 @@
|
||||
import {
|
||||
extract_ignores_above_position,
|
||||
extract_svelte_ignore_from_comments
|
||||
} from './utils/extract_svelte_ignore.js';
|
||||
/* This file is generated by scripts/process-messages/index.js. Do not edit! */
|
||||
|
||||
/** @typedef {Record<string, (...args: any[]) => string>} Warnings */
|
||||
import { getLocator } from 'locate-character';
|
||||
|
||||
/** @satisfies {Warnings} */
|
||||
const css = {
|
||||
/** @param {string} name */
|
||||
'css-unused-selector': (name) => `Unused CSS selector "${name}"`
|
||||
};
|
||||
|
||||
/** @satisfies {Warnings} */
|
||||
const attributes = {
|
||||
'avoid-is': () => 'The "is" attribute is not supported cross-browser and should be avoided',
|
||||
/** @param {string} name */
|
||||
'global-event-reference': (name) =>
|
||||
`You are referencing globalThis.${name}. Did you forget to declare a variable with that name?`,
|
||||
'illegal-attribute-character': () =>
|
||||
"Attributes should not contain ':' characters to prevent ambiguity with Svelte directives",
|
||||
/**
|
||||
* @param {string} wrong
|
||||
* @param {string} right
|
||||
*/
|
||||
'invalid-html-attribute': (wrong, right) =>
|
||||
`'${wrong}' is not a valid HTML attribute. Did you mean '${right}'?`
|
||||
};
|
||||
|
||||
/** @satisfies {Warnings} */
|
||||
const runes = {
|
||||
/** @param {string} name */
|
||||
'store-with-rune-name': (name) =>
|
||||
`It looks like you're using the $${name} rune, but there is a local binding called ${name}. ` +
|
||||
`Referencing a local variable with a $ prefix will create a store subscription. Please rename ${name} to avoid the ambiguity.`,
|
||||
/** @param {string} name */
|
||||
'non-state-reference': (name) =>
|
||||
`${name} is updated, but is not declared with $state(...). Changing its value will not correctly trigger updates.`,
|
||||
'derived-iife': () =>
|
||||
`Use \`$derived.by(() => {...})\` instead of \`$derived((() => {...})());\``,
|
||||
'invalid-props-declaration': () =>
|
||||
`Component properties are declared using $props() in runes mode. Did you forget to call the function?`,
|
||||
'invalid-bindable-declaration': () =>
|
||||
`Bindable component properties are declared using $bindable() in runes mode. Did you forget to call the function?`
|
||||
};
|
||||
|
||||
/** @satisfies {Warnings} */
|
||||
const a11y = {
|
||||
/** @param {string} name */
|
||||
'a11y-aria-attributes': (name) => `A11y: <${name}> should not have aria-* attributes`,
|
||||
/**
|
||||
* @param {string} attribute
|
||||
* @param {string | null} [suggestion]
|
||||
*/
|
||||
'a11y-unknown-aria-attribute': (attribute, suggestion) =>
|
||||
`A11y: Unknown aria attribute 'aria-${attribute}'` +
|
||||
(suggestion ? ` (did you mean '${suggestion}'?)` : ''),
|
||||
/** @param {string} name */
|
||||
'a11y-hidden': (name) => `A11y: <${name}> element should not be hidden`,
|
||||
/**
|
||||
* @param {import('aria-query').ARIAPropertyDefinition} schema
|
||||
* @param {string} attribute
|
||||
*/
|
||||
'a11y-incorrect-aria-attribute-type': (schema, attribute) => {
|
||||
let message;
|
||||
switch (schema.type) {
|
||||
case 'boolean':
|
||||
message = `The value of '${attribute}' must be exactly one of true or false`;
|
||||
break;
|
||||
case 'id':
|
||||
message = `The value of '${attribute}' must be a string that represents a DOM element ID`;
|
||||
break;
|
||||
case 'idlist':
|
||||
message = `The value of '${attribute}' must be a space-separated list of strings that represent DOM element IDs`;
|
||||
break;
|
||||
case 'tristate':
|
||||
message = `The value of '${attribute}' must be exactly one of true, false, or mixed`;
|
||||
break;
|
||||
case 'token':
|
||||
message = `The value of '${attribute}' must be exactly one of ${(schema.values || []).join(
|
||||
', '
|
||||
)}`;
|
||||
break;
|
||||
case 'tokenlist':
|
||||
message = `The value of '${attribute}' must be a space-separated list of one or more of ${(
|
||||
schema.values || []
|
||||
).join(', ')}`;
|
||||
break;
|
||||
default:
|
||||
message = `The value of '${attribute}' must be of type ${schema.type}`;
|
||||
}
|
||||
return `A11y: ${message}`;
|
||||
},
|
||||
'a11y-aria-activedescendant-has-tabindex': () =>
|
||||
'A11y: Elements with attribute aria-activedescendant should have tabindex value',
|
||||
/** @param {string} name */
|
||||
'a11y-misplaced-role': (name) => `A11y: <${name}> should not have role attribute`,
|
||||
/** @param {string | boolean} role */
|
||||
'a11y-no-abstract-role': (role) => `A11y: Abstract role '${role}' is forbidden`,
|
||||
/**
|
||||
* @param {string | boolean} role
|
||||
* @param {string | null} [suggestion]
|
||||
*/
|
||||
'a11y-unknown-role': (role, suggestion) =>
|
||||
`A11y: Unknown role '${role}'` + (suggestion ? ` (did you mean '${suggestion}'?)` : ''),
|
||||
/** @param {string | boolean} role */
|
||||
'a11y-no-redundant-roles': (role) => `A11y: Redundant role '${role}'`,
|
||||
/**
|
||||
* @param {string} role
|
||||
* @param {string[]} props
|
||||
*/
|
||||
'a11y-role-has-required-aria-props': (role, props) =>
|
||||
`A11y: Elements with the ARIA role "${role}" must have the following attributes defined: ${props
|
||||
.map((name) => `"${name}"`)
|
||||
.join(', ')}`,
|
||||
/** @param {string} role */
|
||||
'a11y-interactive-supports-focus': (role) =>
|
||||
`A11y: Elements with the '${role}' interactive role must have a tabindex value.`,
|
||||
/**
|
||||
* @param {string | boolean} role
|
||||
* @param {string} element
|
||||
*/
|
||||
'a11y-no-interactive-element-to-noninteractive-role': (role, element) =>
|
||||
`A11y: <${element}> cannot have role '${role}'`,
|
||||
/**
|
||||
* @param {string | boolean} role
|
||||
* @param {string} element
|
||||
*/
|
||||
'a11y-no-noninteractive-element-to-interactive-role': (role, element) =>
|
||||
`A11y: Non-interactive element <${element}> cannot have interactive role '${role}'`,
|
||||
'a11y-accesskey': () => 'A11y: Avoid using accesskey',
|
||||
'a11y-autofocus': () => 'A11y: Avoid using autofocus',
|
||||
'a11y-misplaced-scope': () => 'A11y: The scope attribute should only be used with <th> elements',
|
||||
'a11y-positive-tabindex': () => 'A11y: avoid tabindex values above zero',
|
||||
'a11y-click-events-have-key-events': () =>
|
||||
'A11y: visible, non-interactive elements with a click event must be accompanied by a keyboard event handler. Consider whether an interactive element such as <button type="button"> or <a> might be more appropriate. See https://svelte.dev/docs/accessibility-warnings#a11y-click-events-have-key-events for more details.',
|
||||
'a11y-no-noninteractive-tabindex': () =>
|
||||
'A11y: noninteractive element cannot have nonnegative tabIndex value',
|
||||
/**
|
||||
* @param {string} attribute
|
||||
* @param {string} role
|
||||
* @param {boolean} is_implicit
|
||||
* @param {string} name
|
||||
*/
|
||||
'a11y-role-supports-aria-props': (attribute, role, is_implicit, name) => {
|
||||
let message = `The attribute '${attribute}' is not supported by the role '${role}'.`;
|
||||
if (is_implicit) {
|
||||
message += ` This role is implicit on the element <${name}>.`;
|
||||
}
|
||||
return `A11y: ${message}`;
|
||||
},
|
||||
/** @param {string} element */
|
||||
'a11y-no-noninteractive-element-interactions': (element) =>
|
||||
`A11y: Non-interactive element <${element}> should not be assigned mouse or keyboard event listeners.`,
|
||||
/**
|
||||
* @param {string} element
|
||||
* @param {string[]} handlers
|
||||
*/
|
||||
'a11y-no-static-element-interactions': (element, handlers) =>
|
||||
`A11y: <${element}> with ${handlers.join(', ')} ${
|
||||
handlers.length === 1 ? 'handler' : 'handlers'
|
||||
} must have an ARIA role`,
|
||||
/**
|
||||
* @param {string} href_attribute
|
||||
* @param {string} href_value
|
||||
*/
|
||||
'a11y-invalid-attribute': (href_attribute, href_value) =>
|
||||
`A11y: '${href_value}' is not a valid ${href_attribute} attribute`,
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {string} article
|
||||
* @param {string} sequence
|
||||
*/
|
||||
'a11y-missing-attribute': (name, article, sequence) =>
|
||||
`A11y: <${name}> element should have ${article} ${sequence} attribute`,
|
||||
/**
|
||||
* @param {null | true | string} type
|
||||
* @param {null | true | string} value
|
||||
*/
|
||||
'a11y-autocomplete-valid': (type, value) =>
|
||||
`A11y: The value '${value}' is not supported by the attribute 'autocomplete' on element <input type="${
|
||||
type || '...'
|
||||
}">`,
|
||||
'a11y-img-redundant-alt': () =>
|
||||
'A11y: Screenreaders already announce <img> elements as an image.',
|
||||
'a11y-label-has-associated-control': () =>
|
||||
'A11y: A form label must be associated with a control.',
|
||||
'a11y-media-has-caption': () => 'A11y: <video> elements must have a <track kind="captions">',
|
||||
/** @param {string} name */
|
||||
'a11y-distracting-elements': (name) => `A11y: Avoid <${name}> elements`,
|
||||
/** @param {boolean} immediate */
|
||||
'a11y-structure': (immediate) =>
|
||||
immediate
|
||||
? 'A11y: <figcaption> must be an immediate child of <figure>'
|
||||
: 'A11y: <figcaption> must be first or last child of <figure>',
|
||||
/**
|
||||
* @param {string} event
|
||||
* @param {string} accompanied_by
|
||||
*/
|
||||
'a11y-mouse-events-have-key-events': (event, accompanied_by) =>
|
||||
`A11y: '${event}' event must be accompanied by '${accompanied_by}' event`,
|
||||
/** @param {string} name */
|
||||
'a11y-missing-content': (name) => `A11y: <${name}> element should have child content`
|
||||
};
|
||||
|
||||
/** @satisfies {Warnings} */
|
||||
const state = {
|
||||
'static-state-reference': () =>
|
||||
`State referenced in its own scope will never update. Did you mean to reference it inside a closure?`,
|
||||
/** @param {string} name */
|
||||
'invalid-rest-eachblock-binding': (name) =>
|
||||
`The rest operator (...) will create a new object and binding '${name}' with the original object will not work`
|
||||
};
|
||||
|
||||
/** @satisfies {Warnings} */
|
||||
const performance = {
|
||||
'avoid-inline-class': () =>
|
||||
`Avoid 'new class' — instead, declare the class at the top level scope`,
|
||||
'avoid-nested-class': () => `Avoid declaring classes below the top level scope`
|
||||
};
|
||||
|
||||
/** @satisfies {Warnings} */
|
||||
const components = {
|
||||
/** @param {string} name */
|
||||
'component-name-lowercase': (name) =>
|
||||
`<${name}> will be treated as an HTML element unless it begins with a capital letter`
|
||||
};
|
||||
|
||||
const legacy = {
|
||||
'no-reactive-declaration': () =>
|
||||
`Reactive declarations only exist at the top level of the instance script`,
|
||||
'module-script-reactive-declaration': () =>
|
||||
'All dependencies of the reactive declaration are declared in a module script and will not be reactive',
|
||||
/** @param {string} name */
|
||||
'unused-export-let': (name) =>
|
||||
`Component has unused export property '${name}'. If it is for external reference only, please consider using \`export const ${name}\``,
|
||||
'deprecated-slot-element': () =>
|
||||
`Using <slot> to render parent content is deprecated. Use {@render ...} tags instead.`,
|
||||
/** @param {string} name */
|
||||
'deprecated-event-handler': (name) =>
|
||||
`Using on:${name} to listen to the ${name} event is is deprecated. Use the event attribute on${name} instead.`,
|
||||
'deprecated-accessors': () =>
|
||||
`The accessors option has been deprecated. It will have no effect in runes mode.`,
|
||||
'deprecated-immutable': () =>
|
||||
`The immutable option has been deprecated. It will have no effect in runes mode.`
|
||||
};
|
||||
|
||||
const block = {
|
||||
'empty-block': () => 'Empty block'
|
||||
};
|
||||
|
||||
const options = {
|
||||
'missing-custom-element-compile-option': () =>
|
||||
"The 'customElement' option is used when generating a custom element. Did you forget the 'customElement: true' compile option?"
|
||||
};
|
||||
|
||||
const misc = {
|
||||
/** @param {string} name */
|
||||
'invalid-self-closing-tag': (name) =>
|
||||
`Self-closing HTML tags for non-void elements are ambiguous — use <${name} ...></${name}> rather than <${name} ... />`
|
||||
};
|
||||
|
||||
/** @satisfies {Warnings} */
|
||||
const warnings = {
|
||||
...css,
|
||||
...attributes,
|
||||
...runes,
|
||||
...a11y,
|
||||
...performance,
|
||||
...state,
|
||||
...components,
|
||||
...legacy,
|
||||
...block,
|
||||
...options,
|
||||
...misc
|
||||
};
|
||||
|
||||
/** @typedef {typeof warnings} AllWarnings */
|
||||
/** @typedef {{ start?: number, end?: number }} NodeLike */
|
||||
/** @type {import('#compiler').Warning[]} */
|
||||
let warnings = [];
|
||||
/** @type {string | undefined} */
|
||||
let filename;
|
||||
let locator = getLocator('', { offsetLine: 1 });
|
||||
|
||||
/**
|
||||
* @template {keyof AllWarnings} T
|
||||
* @param {import('./phases/types').RawWarning[]} array the array to push the warning to, if not ignored
|
||||
* @param {{ start?: number, end?: number, type?: string, parent?: import('#compiler').SvelteNode | null, leadingComments?: import('estree').Comment[] } | null} node the node related to the warning
|
||||
* @param {import('#compiler').SvelteNode[]} path the path to the node, so that we can traverse upwards to find svelte-ignore comments
|
||||
* @param {T} code the warning code
|
||||
* @param {Parameters<AllWarnings[T]>} args the arguments to pass to the warning function
|
||||
* @returns {void}
|
||||
* @param {{
|
||||
* source: string;
|
||||
* filename: string | undefined;
|
||||
* }} options
|
||||
* @returns {import('#compiler').Warning[]}
|
||||
*/
|
||||
export function warn(array, node, path, code, ...args) {
|
||||
const fn = warnings[code];
|
||||
|
||||
// Traverse the AST upwards to find any svelte-ignore comments.
|
||||
// This assumes that people don't have their code littered with warnings,
|
||||
// at which point this might become inefficient.
|
||||
/** @type {string[]} */
|
||||
const ignores = [];
|
||||
|
||||
if (node) {
|
||||
// comments inside JavaScript (estree)
|
||||
if ('leadingComments' in node) {
|
||||
ignores.push(...extract_svelte_ignore_from_comments(node));
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = path.length - 1; i >= 0; i--) {
|
||||
const current = path[i];
|
||||
|
||||
// comments inside JavaScript (estree)
|
||||
if ('leadingComments' in current) {
|
||||
ignores.push(...extract_svelte_ignore_from_comments(current));
|
||||
}
|
||||
|
||||
// Svelte nodes
|
||||
if (current.type === 'Fragment') {
|
||||
ignores.push(
|
||||
...extract_ignores_above_position(
|
||||
/** @type {import('#compiler').TemplateNode} */ (path[i + 1] ?? node),
|
||||
current.nodes
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Style nodes
|
||||
if (current.type === 'StyleSheet' && current.content.comment) {
|
||||
ignores.push(...current.content.comment.ignores);
|
||||
}
|
||||
}
|
||||
|
||||
if (ignores.includes(code)) return;
|
||||
export function reset_warnings(options) {
|
||||
filename = options.filename;
|
||||
locator = getLocator(options.source, { offsetLine: 1 });
|
||||
return warnings = [];
|
||||
}
|
||||
|
||||
const start = node?.start;
|
||||
const end = node?.end;
|
||||
/**
|
||||
* @param {null | NodeLike} node
|
||||
* @param {string} code
|
||||
* @param {string} message
|
||||
*/
|
||||
function w(node, code, message) {
|
||||
// @ts-expect-error
|
||||
if (node.ignores?.has(code)) return;
|
||||
|
||||
array.push({
|
||||
warnings.push({
|
||||
code,
|
||||
// @ts-expect-error
|
||||
message: fn(...args),
|
||||
position: start !== undefined && end !== undefined ? [start, end] : undefined
|
||||
message,
|
||||
filename,
|
||||
start: node?.start !== undefined ? locator(node.start) : undefined,
|
||||
end: node?.end !== undefined ? locator(node.end) : undefined
|
||||
});
|
||||
}
|
||||
}
|
@ -1,2 +1,8 @@
|
||||
import type { Bindable } from '../index.js';
|
||||
|
||||
/** Anything except a function */
|
||||
export type NotFunction<T> = T extends Function ? never : T;
|
||||
|
||||
export type RemoveBindable<Props extends Record<string, any>> = {
|
||||
[Key in keyof Props]: Props[Key] extends Bindable<infer Value> ? Value : Props[Key];
|
||||
};
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue