From 4cc013fa6f33523e6753c686c440902acc7af9b7 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 23 Apr 2024 14:08:01 -0400 Subject: [PATCH] centralise stuff --- packages/svelte/src/compiler/index.js | 8 +- .../src/compiler/phases/2-analyze/a11y.js | 90 ++++++++----------- .../compiler/phases/2-analyze/css/css-warn.js | 9 +- .../src/compiler/phases/2-analyze/index.js | 33 +++---- .../compiler/phases/2-analyze/validation.js | 50 ++++------- .../src/compiler/phases/3-transform/index.js | 43 +-------- .../svelte/src/compiler/phases/types.d.ts | 10 +-- packages/svelte/src/compiler/warnings-tmp.js | 12 +-- 8 files changed, 88 insertions(+), 167 deletions(-) diff --git a/packages/svelte/src/compiler/index.js b/packages/svelte/src/compiler/index.js index eee35bf2a8..c2516a2e5f 100644 --- a/packages/svelte/src/compiler/index.js +++ b/packages/svelte/src/compiler/index.js @@ -8,6 +8,7 @@ import { remove_typescript_nodes } from './phases/1-parse/remove_typescript_node import { analyze_component, analyze_module } from './phases/2-analyze/index.js'; import { transform_component, transform_module } from './phases/3-transform/index.js'; import { validate_component_options, validate_module_options } from './validate-options.js'; +import { reset_warnings } from './warnings.js'; export { default as preprocess } from './preprocess/index.js'; /** @@ -20,6 +21,7 @@ export { default as preprocess } from './preprocess/index.js'; */ export function compile(source, options) { try { + const warnings = reset_warnings({ source, filename: options.filename }); const validated = validate_component_options(options, ''); let parsed = _parse(source); @@ -44,6 +46,7 @@ export function compile(source, options) { const analysis = analyze_component(parsed, source, combined_options); const result = transform_component(analysis, source, combined_options); + result.warnings = warnings; result.ast = to_public_ast(source, parsed, options.modernAst); return result; } catch (e) { @@ -65,9 +68,12 @@ export function compile(source, options) { */ export function compileModule(source, options) { try { + const warnings = reset_warnings({ source, filename: options.filename }); const validated = validate_module_options(options, ''); const analysis = analyze_module(parse_acorn(source, false), validated); - return transform_module(analysis, source, validated); + const result = transform_module(analysis, source, validated); + result.warnings = warnings; + return result; } catch (e) { if (e instanceof CompileError) { handle_compile_error(e, options.filename, source); diff --git a/packages/svelte/src/compiler/phases/2-analyze/a11y.js b/packages/svelte/src/compiler/phases/2-analyze/a11y.js index c31f20af40..17b4a94eba 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/a11y.js +++ b/packages/svelte/src/compiler/phases/2-analyze/a11y.js @@ -672,15 +672,6 @@ function check_element(node, state) { // foreign namespace means elements can have completely different meanings, therefore we don't check them if (state.options.namespace === 'foreign') return; - /** - * @template {keyof import('../../warnings-tmp.js').AllWarnings} T - * @param {{ start?: number, end?: number }} node - * @param {T} code - * @param {Parameters} args - * @returns {void} - */ - const push_warning = (node, code, ...args) => warn(state.analysis.warnings, node, code, ...args); - /** @type {Map} */ const attribute_map = new Map(); @@ -727,17 +718,17 @@ function check_element(node, state) { if (name.startsWith('aria-')) { if (invisible_elements.includes(node.name)) { // aria-unsupported-elements - push_warning(attribute, 'a11y-aria-attributes', node.name); + warn(attribute, 'a11y-aria-attributes', node.name); } const type = name.slice(5); if (!aria_attributes.includes(type)) { const match = fuzzymatch(type, aria_attributes); - push_warning(attribute, 'a11y-unknown-aria-attribute', type, match); + warn(attribute, 'a11y-unknown-aria-attribute', type, match); } if (name === 'aria-hidden' && regex_heading_tags.test(node.name)) { - push_warning(attribute, 'a11y-hidden', node.name); + warn(attribute, 'a11y-hidden', node.name); } // aria-proptypes @@ -747,7 +738,7 @@ function check_element(node, state) { if (value !== null && value !== undefined) { const schema = aria.get(/** @type {import('aria-query').ARIAProperty} */ (name)); if (schema !== undefined && !is_valid_aria_attribute_value(schema, value)) { - push_warning(attribute, 'a11y-incorrect-aria-attribute-type', schema, name); + warn(attribute, 'a11y-incorrect-aria-attribute-type', schema, name); } } @@ -758,7 +749,7 @@ function check_element(node, state) { !is_interactive_element(node.name, attribute_map) && !attribute_map.has('tabindex') ) { - push_warning(attribute, 'a11y-aria-activedescendant-has-tabindex'); + warn(attribute, 'a11y-aria-activedescendant-has-tabindex'); } } @@ -766,7 +757,7 @@ function check_element(node, state) { if (name === 'role') { if (invisible_elements.includes(node.name)) { // aria-unsupported-elements - push_warning(attribute, 'a11y-misplaced-role', node.name); + warn(attribute, 'a11y-misplaced-role', node.name); } const value = get_static_value(attribute); @@ -776,10 +767,10 @@ function check_element(node, state) { /** @type {import('aria-query').ARIARoleDefinitionKey} current_role */ (c_r); if (current_role && is_abstract_role(current_role)) { - push_warning(attribute, 'a11y-no-abstract-role', current_role); + warn(attribute, 'a11y-no-abstract-role', current_role); } else if (current_role && !aria_roles.includes(current_role)) { const match = fuzzymatch(current_role, aria_roles); - push_warning(attribute, 'a11y-unknown-role', current_role, match); + warn(attribute, 'a11y-unknown-role', current_role, match); } // no-redundant-roles @@ -788,7 +779,7 @@ function check_element(node, state) { //
    is ok because CSS list-style:none removes the semantics and this is a way to bring them back !['ul', 'ol', 'li'].includes(node.name) ) { - push_warning(attribute, 'a11y-no-redundant-roles', current_role); + warn(attribute, 'a11y-no-redundant-roles', current_role); } // Footers and headers are special cases, and should not have redundant roles unless they are the children of sections or articles. @@ -797,7 +788,7 @@ function check_element(node, state) { const has_nested_redundant_role = current_role === a11y_nested_implicit_semantics.get(node.name); if (has_nested_redundant_role) { - push_warning(attribute, 'a11y-no-redundant-roles', current_role); + warn(attribute, 'a11y-no-redundant-roles', current_role); } } @@ -813,7 +804,7 @@ function check_element(node, state) { (prop) => !attributes.find((a) => a.name === prop) ); if (has_missing_props) { - push_warning( + warn( attribute, 'a11y-role-has-required-aria-props', current_role, @@ -836,7 +827,7 @@ function check_element(node, state) { a11y_interactive_handlers.includes(handler) ); if (has_interactive_handlers) { - push_warning(node, 'a11y-interactive-supports-focus', current_role); + warn(node, 'a11y-interactive-supports-focus', current_role); } } @@ -845,7 +836,7 @@ function check_element(node, state) { is_interactive_element(node.name, attribute_map) && (is_non_interactive_roles(current_role) || is_presentation_role(current_role)) ) { - push_warning( + warn( node, 'a11y-no-interactive-element-to-noninteractive-role', current_role, @@ -861,7 +852,7 @@ function check_element(node, state) { current_role ) ) { - push_warning( + warn( node, 'a11y-no-noninteractive-element-to-interactive-role', current_role, @@ -874,17 +865,17 @@ function check_element(node, state) { // no-access-key if (name === 'accesskey') { - push_warning(attribute, 'a11y-accesskey'); + warn(attribute, 'a11y-accesskey'); } // no-autofocus if (name === 'autofocus') { - push_warning(attribute, 'a11y-autofocus'); + warn(attribute, 'a11y-autofocus'); } // scope if (name === 'scope' && !is_dynamic_element && node.name !== 'th') { - push_warning(attribute, 'a11y-misplaced-scope'); + warn(attribute, 'a11y-misplaced-scope'); } // tabindex-no-positive @@ -892,7 +883,7 @@ function check_element(node, state) { const value = get_static_value(attribute); // @ts-ignore todo is tabindex=true correct case? if (!isNaN(value) && +value > 0) { - push_warning(attribute, 'a11y-positive-tabindex'); + warn(attribute, 'a11y-positive-tabindex'); } } } @@ -916,7 +907,7 @@ function check_element(node, state) { const has_key_event = handlers.has('keydown') || handlers.has('keyup') || handlers.has('keypress'); if (!has_key_event) { - push_warning(node, 'a11y-click-events-have-key-events'); + warn(node, 'a11y-click-events-have-key-events'); } } } @@ -934,7 +925,7 @@ function check_element(node, state) { const tab_index = attribute_map.get('tabindex'); const tab_index_value = get_static_text_value(tab_index); if (tab_index && (tab_index_value === null || Number(tab_index_value) >= 0)) { - push_warning(node, 'a11y-no-noninteractive-tabindex'); + warn(node, 'a11y-no-noninteractive-tabindex'); } } @@ -949,14 +940,7 @@ function check_element(node, state) { if ( invalid_aria_props.includes(/** @type {import('aria-query').ARIAProperty} */ (attr.name)) ) { - push_warning( - attr, - 'a11y-role-supports-aria-props', - attr.name, - role_value, - is_implicit, - node.name - ); + warn(attr, 'a11y-role-supports-aria-props', attr.name, role_value, is_implicit, node.name); } } } @@ -974,7 +958,7 @@ function check_element(node, state) { a11y_recommended_interactive_handlers.includes(handler) ); if (has_interactive_handlers) { - push_warning(node, 'a11y-no-noninteractive-element-interactions', node.name); + warn(node, 'a11y-no-noninteractive-element-interactions', node.name); } } @@ -993,16 +977,16 @@ function check_element(node, state) { a11y_interactive_handlers.includes(handler) ); if (interactive_handlers.length > 0) { - push_warning(node, 'a11y-no-static-element-interactions', node.name, interactive_handlers); + warn(node, 'a11y-no-static-element-interactions', node.name, interactive_handlers); } } if (handlers.has('mouseover') && !handlers.has('focus')) { - push_warning(node, 'a11y-mouse-events-have-key-events', 'mouseover', 'focus'); + warn(node, 'a11y-mouse-events-have-key-events', 'mouseover', 'focus'); } if (handlers.has('mouseout') && !handlers.has('blur')) { - push_warning(node, 'a11y-mouse-events-have-key-events', 'mouseout', 'blur'); + warn(node, 'a11y-mouse-events-have-key-events', 'mouseout', 'blur'); } // element-specific checks @@ -1021,14 +1005,14 @@ function check_element(node, state) { const href_value = get_static_text_value(href); if (href_value !== null) { if (href_value === '' || href_value === '#' || /^\W*javascript:/i.test(href_value)) { - push_warning(href, 'a11y-invalid-attribute', href.name, href_value); + warn(href, 'a11y-invalid-attribute', href.name, href_value); } } } else if (!has_spread) { const id_attribute = get_static_value(attribute_map.get('id')); const name_attribute = get_static_value(attribute_map.get('name')); if (!id_attribute && !name_attribute) { - push_warning(...warn_missing_attribute(node, ['href'])); + warn(...warn_missing_attribute(node, ['href'])); } } } else if (!has_spread) { @@ -1036,7 +1020,7 @@ function check_element(node, state) { if (required_attributes) { const has_attribute = required_attributes.some((name) => attribute_map.has(name)); if (!has_attribute) { - push_warning(...warn_missing_attribute(node, required_attributes)); + warn(...warn_missing_attribute(node, required_attributes)); } } } @@ -1048,7 +1032,7 @@ function check_element(node, state) { const required_attributes = ['alt', 'aria-label', 'aria-labelledby']; const has_attribute = required_attributes.some((name) => attribute_map.has(name)); if (!has_attribute) { - push_warning(...warn_missing_attribute(node, required_attributes, 'input type="image"')); + warn(...warn_missing_attribute(node, required_attributes, 'input type="image"')); } } // autocomplete-valid @@ -1056,7 +1040,7 @@ function check_element(node, state) { if (type && autocomplete) { const autocomplete_value = get_static_value(autocomplete); if (!is_valid_autocomplete(autocomplete_value)) { - push_warning(autocomplete, 'a11y-autocomplete-valid', type_value, autocomplete_value); + warn(autocomplete, 'a11y-autocomplete-valid', type_value, autocomplete_value); } } } @@ -1066,7 +1050,7 @@ function check_element(node, state) { const aria_hidden = get_static_value(attribute_map.get('aria-hidden')); if (alt_attribute && !aria_hidden) { if (/\b(image|picture|photo)\b/i.test(alt_attribute)) { - push_warning(node, 'a11y-img-redundant-alt'); + warn(node, 'a11y-img-redundant-alt'); } } } @@ -1096,7 +1080,7 @@ function check_element(node, state) { return has; }; if (!attribute_map.has('for') && !has_input_child(node)) { - push_warning(node, 'a11y-label-has-associated-control'); + warn(node, 'a11y-label-has-associated-control'); } } @@ -1118,13 +1102,13 @@ function check_element(node, state) { ); } if (!has_caption) { - push_warning(node, 'a11y-media-has-caption'); + warn(node, 'a11y-media-has-caption'); } } if (node.name === 'figcaption') { if (!is_parent(node.parent, ['figure'])) { - push_warning(node, 'a11y-structure', true); + warn(node, 'a11y-structure', true); } } @@ -1138,13 +1122,13 @@ function check_element(node, state) { (child) => child.type === 'RegularElement' && child.name === 'figcaption' ); if (index !== -1 && index !== 0 && index !== children.length - 1) { - push_warning(children[index], 'a11y-structure', false); + warn(children[index], 'a11y-structure', false); } } if (a11y_distracting_elements.includes(node.name)) { // no-distracting-elements - push_warning(node, 'a11y-distracting-elements', node.name); + warn(node, 'a11y-distracting-elements', node.name); } // Check content @@ -1154,7 +1138,7 @@ function check_element(node, state) { a11y_required_content.includes(node.name) && node.fragment.nodes.length === 0 ) { - push_warning(node, 'a11y-missing-content', node.name); + warn(node, 'a11y-missing-content', node.name); } } diff --git a/packages/svelte/src/compiler/phases/2-analyze/css/css-warn.js b/packages/svelte/src/compiler/phases/2-analyze/css/css-warn.js index d2514187a5..ddb5d510c6 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/css/css-warn.js +++ b/packages/svelte/src/compiler/phases/2-analyze/css/css-warn.js @@ -4,13 +4,12 @@ import { is_keyframes_node } from '../../css.js'; /** * @param {import('#compiler').Css.StyleSheet} stylesheet - * @param {import('../../types.js').RawWarning[]} warnings */ -export function warn_unused(stylesheet, warnings) { - walk(stylesheet, { warnings, stylesheet }, visitors); +export function warn_unused(stylesheet) { + walk(stylesheet, { stylesheet }, visitors); } -/** @type {import('zimmerframe').Visitors} */ +/** @type {import('zimmerframe').Visitors} */ const visitors = { Atrule(node, context) { if (!is_keyframes_node(node)) { @@ -26,7 +25,7 @@ const visitors = { if (!node.metadata.used) { const content = context.state.stylesheet.content; const text = content.styles.substring(node.start - content.start, node.end - content.start); - warn(context.state.warnings, node, 'css-unused-selector', text); + warn(node, 'css-unused-selector', text); } context.next(); diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js index 57126747d8..8ce247889d 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/index.js @@ -25,6 +25,7 @@ import { prune } from './css/css-prune.js'; import { hash } from './utils.js'; import { warn_unused } from './css/css-warn.js'; import { extract_svelte_ignore } from '../../utils/extract_svelte_ignore.js'; +import { reset_warnings } from '../../warnings.js'; /** * @param {import('#compiler').Script | null} script @@ -234,16 +235,9 @@ export function analyze_module(ast, options) { } } - /** @type {import('../types').RawWarning[]} */ - const warnings = []; - - const analysis = { - warnings - }; - walk( /** @type {import('estree').Node} */ (ast), - { scope, analysis }, + { scope }, // @ts-expect-error TODO clean this mess up merge(set_scope(scopes), validation_runes_js, runes_scope_js_tweaker) ); @@ -251,7 +245,6 @@ export function analyze_module(ast, options) { return { module: { ast, scope, scopes }, name: options.filename || 'module', - warnings, accessors: false, runes: true, immutable: true @@ -275,9 +268,6 @@ export function analyze_component(root, source, options) { /** @type {import('../types.js').Template} */ const template = { ast: root.fragment, scope, scopes }; - /** @type {import('../types').RawWarning[]} */ - const warnings = []; - // create synthetic bindings for store subscriptions for (const [name, references] of module.scope.references) { if (name[0] !== '$' || ReservedKeywords.includes(name)) continue; @@ -331,7 +321,7 @@ export function analyze_component(root, source, options) { } else if (declaration !== null && Runes.includes(/** @type {any} */ (name))) { for (const { node, path } of references) { if (path.at(-1)?.type === 'CallExpression') { - warn(warnings, node, 'store-with-rune-name', store_name); + warn(node, 'store-with-rune-name', store_name); } } } @@ -391,7 +381,6 @@ export function analyze_component(root, source, options) { reactive_statements: new Map(), binding_groups: new Map(), slot_names: new Map(), - warnings, css: { ast: root.css, hash: root.css @@ -408,7 +397,7 @@ export function analyze_component(root, source, options) { }; if (!options.customElement && root.options?.customElement) { - warn(analysis.warnings, root.options, 'missing-custom-element-compile-option'); + warn(root.options, 'missing-custom-element-compile-option'); } if (analysis.runes) { @@ -498,7 +487,7 @@ export function analyze_component(root, source, options) { (r) => r.node !== binding.node && r.path.at(-1)?.type !== 'ExportSpecifier' ); if (!references.length && !instance.scope.declarations.has(`$${name}`)) { - warn(warnings, binding.node, 'unused-export-let', name); + warn(binding.node, 'unused-export-let', name); } } } @@ -538,7 +527,7 @@ export function analyze_component(root, source, options) { type === 'AwaitBlock' || type === 'KeyBlock' ) { - warn(warnings, binding.node, 'non-state-reference', name); + warn(binding.node, 'non-state-reference', name); continue outer; } } @@ -546,7 +535,7 @@ export function analyze_component(root, source, options) { } } - warn(warnings, binding.node, 'non-state-reference', name); + warn(binding.node, 'non-state-reference', name); continue outer; } } @@ -565,7 +554,7 @@ export function analyze_component(root, source, options) { !analysis.css.ast.content.comment || !extract_svelte_ignore(analysis.css.ast.content.comment.data).includes('css-unused-selector') ) { - warn_unused(analysis.css.ast, analysis.warnings); + warn_unused(analysis.css.ast); } outer: for (const element of analysis.elements) { @@ -688,7 +677,7 @@ const legacy_scope_tweaker = { (d) => d.scope === state.analysis.module.scope && d.declaration_kind !== 'const' ) ) { - warn(state.analysis.warnings, node, 'module-script-reactive-declaration'); + warn(node, 'module-script-reactive-declaration'); } if ( @@ -874,7 +863,7 @@ const legacy_scope_tweaker = { } }; -/** @type {import('zimmerframe').Visitors} */ +/** @type {import('zimmerframe').Visitors} */ const runes_scope_js_tweaker = { VariableDeclarator(node, { state }) { if (node.init?.type !== 'CallExpression') return; @@ -1210,7 +1199,7 @@ const common_visitors = { binding.kind === 'derived') && context.state.function_depth === binding.scope.function_depth ) { - warn(context.state.analysis.warnings, node, 'static-state-reference'); + warn(node, 'static-state-reference'); } } }, diff --git a/packages/svelte/src/compiler/phases/2-analyze/validation.js b/packages/svelte/src/compiler/phases/2-analyze/validation.js index 0a0b214cd7..e899f05693 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/validation.js +++ b/packages/svelte/src/compiler/phases/2-analyze/validation.js @@ -131,12 +131,7 @@ function validate_element(node, context) { value.name === attribute.name && !context.state.scope.get(value.name) ) { - warn( - context.state.analysis.warnings, - attribute, - 'global-event-reference', - attribute.name - ); + warn(attribute, 'global-event-reference', attribute.name); } } @@ -146,18 +141,12 @@ function validate_element(node, context) { } if (attribute.name === 'is' && context.state.options.namespace !== 'foreign') { - warn(context.state.analysis.warnings, attribute, 'avoid-is'); + warn(attribute, 'avoid-is'); } const correct_name = react_attributes.get(attribute.name); if (correct_name) { - warn( - context.state.analysis.warnings, - attribute, - 'invalid-html-attribute', - attribute.name, - correct_name - ); + warn(attribute, 'invalid-html-attribute', attribute.name, correct_name); } validate_attribute_name(attribute, context); @@ -233,7 +222,7 @@ function validate_attribute_name(attribute, context) { !attribute.name.startsWith('xlink:') && !attribute.name.startsWith('xml:') ) { - warn(context.state.analysis.warnings, attribute, 'illegal-attribute-character'); + warn(attribute, 'illegal-attribute-character'); } } @@ -311,7 +300,7 @@ function validate_block_not_empty(node, context) { // Assumption: If the block has zero elements, someone's in the middle of typing it out, // so don't warn in that case because it would be distracting. if (node.nodes.length === 1 && node.nodes[0].type === 'Text' && !node.nodes[0].raw.trim()) { - warn(context.state.analysis.warnings, node.nodes[0], 'empty-block'); + warn(node.nodes[0], 'empty-block'); } } @@ -372,12 +361,7 @@ const validation = { } if (binding?.kind === 'each' && binding.metadata?.inside_rest) { - warn( - context.state.analysis.warnings, - binding.node, - 'invalid-rest-eachblock-binding', - binding.node.name - ); + warn(binding.node, 'invalid-rest-eachblock-binding', binding.node.name); } const parent = context.path.at(-1); @@ -531,7 +515,7 @@ const validation = { binding.declaration_kind === 'import' && binding.references.length === 0 ) { - warn(context.state.analysis.warnings, node, 'component-name-lowercase', node.name); + warn(node, 'component-name-lowercase', node.name); } validate_element(node, context); @@ -570,7 +554,7 @@ const validation = { !VoidElements.includes(node.name) && !SVGElements.includes(node.name) ) { - warn(context.state.analysis.warnings, node, 'invalid-self-closing-tag', node.name); + warn(node, 'invalid-self-closing-tag', node.name); } context.next({ @@ -766,7 +750,7 @@ export const validation_legacy = merge(validation, a11y_validators, { (state.ast_type !== 'instance' || /** @type {import('#compiler').SvelteNode} */ (path.at(-1)).type !== 'Program') ) { - warn(state.analysis.warnings, node, 'no-reactive-declaration'); + warn(node, 'no-reactive-declaration'); } }, UpdateExpression(node, { state }) { @@ -969,12 +953,12 @@ export const validation_runes_js = { const allowed_depth = context.state.ast_type === 'module' ? 0 : 1; if (context.state.scope.function_depth > allowed_depth) { - warn(context.state.analysis.warnings, node, 'avoid-nested-class'); + warn(node, 'avoid-nested-class'); } }, NewExpression(node, context) { if (node.callee.type === 'ClassExpression' && context.state.scope.function_depth > 0) { - warn(context.state.analysis.warnings, node, 'avoid-inline-class'); + warn(node, 'avoid-inline-class'); } } }; @@ -1127,7 +1111,7 @@ export const validation_runes = merge(validation, a11y_validators, { if (rune === null) { if (init?.type === 'Identifier' && init.name === '$props' && !state.scope.get('props')) { - warn(state.analysis.warnings, node, 'invalid-props-declaration'); + warn(node, 'invalid-props-declaration'); } return; } @@ -1180,7 +1164,7 @@ export const validation_runes = merge(validation, a11y_validators, { arg.type === 'CallExpression' && (arg.callee.type === 'ArrowFunctionExpression' || arg.callee.type === 'FunctionExpression') ) { - warn(state.analysis.warnings, node, 'derived-iife'); + warn(node, 'derived-iife'); } } }, @@ -1190,19 +1174,19 @@ export const validation_runes = merge(validation, a11y_validators, { node.right.name === '$bindable' && !state.scope.get('bindable') ) { - warn(state.analysis.warnings, node, 'invalid-bindable-declaration'); + warn(node, 'invalid-bindable-declaration'); } }, SlotElement(node, { state }) { if (!state.analysis.custom_element) { - warn(state.analysis.warnings, node, 'deprecated-slot-element'); + warn(node, 'deprecated-slot-element'); } }, - OnDirective(node, { state, path }) { + OnDirective(node, { path }) { const parent_type = path.at(-1)?.type; // Don't warn on component events; these might not be under the author's control so the warning would be unactionable if (parent_type === 'RegularElement' || parent_type === 'SvelteElement') { - warn(state.analysis.warnings, node, 'deprecated-event-handler', node.name); + warn(node, 'deprecated-event-handler', node.name); } }, // TODO this is a code smell. need to refactor this stuff diff --git a/packages/svelte/src/compiler/phases/3-transform/index.js b/packages/svelte/src/compiler/phases/3-transform/index.js index 2334b3318d..2c4adb3dc8 100644 --- a/packages/svelte/src/compiler/phases/3-transform/index.js +++ b/packages/svelte/src/compiler/phases/3-transform/index.js @@ -17,7 +17,7 @@ export function transform_component(analysis, source, options) { return { js: /** @type {any} */ (null), css: null, - warnings: transform_warnings(source, options.filename, analysis.warnings), + warnings: /** @type {any} */ (null), // set afterwards metadata: { runes: analysis.runes }, @@ -60,7 +60,7 @@ export function transform_component(analysis, source, options) { return { js, css, - warnings: transform_warnings(source, options.filename, analysis.warnings), // TODO apply preprocessor sourcemap + warnings: /** @type {any} */ (null), // set afterwards. TODO apply preprocessor sourcemap metadata: { runes: analysis.runes }, @@ -79,7 +79,7 @@ export function transform_module(analysis, source, options) { return { js: /** @type {any} */ (null), css: null, - warnings: transform_warnings(source, analysis.name, analysis.warnings), + warnings: /** @type {any} */ (null), // set afterwards metadata: { runes: true }, @@ -105,45 +105,10 @@ export function transform_module(analysis, source, options) { return { js: print(program, {}), css: null, - warnings: transform_warnings(source, analysis.name, analysis.warnings), metadata: { runes: true }, + warnings: /** @type {any} */ (null), // set afterwards ast: /** @type {any} */ (null) // set afterwards }; } - -/** - * @param {string} source - * @param {string | undefined} name - * @param {import('../types').RawWarning[]} warnings - * @returns {import('#compiler').Warning[]} - */ -function transform_warnings(source, name, warnings) { - if (warnings.length === 0) return []; - - const locate = getLocator(source, { offsetLine: 1 }); - - /** @type {import('#compiler').Warning[]} */ - const result = []; - - for (const warning of warnings) { - const start = - warning.position && - /** @type {import('locate-character').Location} */ (locate(warning.position[0])); - - const end = - warning.position && - /** @type {import('locate-character').Location} */ (locate(warning.position[1])); - - result.push({ - start, - end, - filename: name, - message: warning.message, - code: warning.code - }); - } - - return result; -} diff --git a/packages/svelte/src/compiler/phases/types.d.ts b/packages/svelte/src/compiler/phases/types.d.ts index 91fd68ca39..c2ece7d7fd 100644 --- a/packages/svelte/src/compiler/phases/types.d.ts +++ b/packages/svelte/src/compiler/phases/types.d.ts @@ -6,7 +6,8 @@ import type { SlotElement, SvelteElement, SvelteNode, - SvelteOptions + SvelteOptions, + Warning } from '#compiler'; import type { Identifier, LabeledStatement, Program } from 'estree'; import type { Scope, ScopeRoot } from './scope.js'; @@ -28,19 +29,12 @@ export interface ReactiveStatement { dependencies: Binding[]; } -export interface RawWarning { - code: string; - message: string; - position: [number, number] | undefined; -} - /** * Analysis common to modules and components */ export interface Analysis { module: Js; name: string; // TODO should this be filename? it's used in `compileModule` as well as `compile` - warnings: RawWarning[]; runes: boolean; immutable: boolean; diff --git a/packages/svelte/src/compiler/warnings-tmp.js b/packages/svelte/src/compiler/warnings-tmp.js index 3983d3686e..1249b6693d 100644 --- a/packages/svelte/src/compiler/warnings-tmp.js +++ b/packages/svelte/src/compiler/warnings-tmp.js @@ -1,3 +1,5 @@ +import { warnings as array, filename, locator } from './warnings.js'; + /** @typedef {Record string>} Warnings */ /** @satisfies {Warnings} */ @@ -270,25 +272,23 @@ const warnings = { /** * @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 {T} code the warning code * @param {Parameters} args the arguments to pass to the warning function * @returns {void} */ -export function warn(array, node, code, ...args) { +export function warn(node, code, ...args) { // @ts-expect-error if (node.ignores?.has(code)) return; const fn = warnings[code]; - const start = node?.start; - const end = node?.end; - array.push({ code, // @ts-expect-error message: fn(...args), - position: start !== undefined && end !== undefined ? [start, end] : undefined + filename, + start: node?.start !== undefined ? locator(node.start) : undefined, + end: node?.end !== undefined ? locator(node.end) : undefined }); }