From 8de7931c64b46d22fc303adce14eb3f680c8ca66 Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Fri, 7 Oct 2022 08:23:12 +0800 Subject: [PATCH 01/27] [feat] better error message for invalid logic block placement (#7862) * better error message for invalid logic block placement * include checking for {@html} tags in invalid location --- src/compiler/parse/errors.ts | 8 ++++++++ src/compiler/parse/state/tag.ts | 19 ++++++++++++++++--- .../html-block-in-attribute/errors.json | 9 +++++++++ .../html-block-in-attribute/input.svelte | 1 + .../html-block-in-textarea/errors.json | 9 +++++++++ .../html-block-in-textarea/input.svelte | 3 +++ .../logic-block-in-attribute/errors.json | 9 +++++++++ .../logic-block-in-attribute/input.svelte | 1 + .../logic-block-in-textarea/errors.json | 9 +++++++++ .../logic-block-in-textarea/input.svelte | 5 +++++ 10 files changed, 70 insertions(+), 3 deletions(-) create mode 100644 test/validator/samples/html-block-in-attribute/errors.json create mode 100644 test/validator/samples/html-block-in-attribute/input.svelte create mode 100644 test/validator/samples/html-block-in-textarea/errors.json create mode 100644 test/validator/samples/html-block-in-textarea/input.svelte create mode 100644 test/validator/samples/logic-block-in-attribute/errors.json create mode 100644 test/validator/samples/logic-block-in-attribute/input.svelte create mode 100644 test/validator/samples/logic-block-in-textarea/errors.json create mode 100644 test/validator/samples/logic-block-in-textarea/input.svelte diff --git a/src/compiler/parse/errors.ts b/src/compiler/parse/errors.ts index 63bd5b0919..26f03c0cb1 100644 --- a/src/compiler/parse/errors.ts +++ b/src/compiler/parse/errors.ts @@ -107,6 +107,14 @@ export default { code: `invalid-${slug}-placement`, message: `<${name}> tags cannot be inside elements or blocks` }), + invalid_logic_block_placement: (location: string, name: string) => ({ + code: 'invalid-logic-block-placement', + message: `{#${name}} logic block cannot be ${location}` + }), + invalid_tag_placement: (location: string, name: string) => ({ + code: 'invalid-tag-placement', + message: `{@${name}} tag cannot be ${location}` + }), invalid_ref_directive: (name: string) => ({ code: 'invalid-ref-directive', message: `The ref directive is no longer supported — use \`bind:this={${name}}\` instead` diff --git a/src/compiler/parse/state/tag.ts b/src/compiler/parse/state/tag.ts index efce375b7e..4be47f25b0 100644 --- a/src/compiler/parse/state/tag.ts +++ b/src/compiler/parse/state/tag.ts @@ -219,7 +219,8 @@ export default function tag(parser: Parser) { element.children = read_sequence( parser, () => - /^<\/textarea(\s[^>]*)?>/i.test(parser.template.slice(parser.index)) + /^<\/textarea(\s[^>]*)?>/i.test(parser.template.slice(parser.index)), + 'inside \ No newline at end of file diff --git a/test/validator/samples/logic-block-in-attribute/errors.json b/test/validator/samples/logic-block-in-attribute/errors.json new file mode 100644 index 0000000000..ebf68938e7 --- /dev/null +++ b/test/validator/samples/logic-block-in-attribute/errors.json @@ -0,0 +1,9 @@ +[ + { + "code": "invalid-logic-block-placement", + "message": "{#if} logic block cannot be in attribute value", + "start": { "line": 1, "column": 12, "character": 12 }, + "end": { "line": 1, "column": 12, "character": 12 }, + "pos": 12 + } +] diff --git a/test/validator/samples/logic-block-in-attribute/input.svelte b/test/validator/samples/logic-block-in-attribute/input.svelte new file mode 100644 index 0000000000..241529527c --- /dev/null +++ b/test/validator/samples/logic-block-in-attribute/input.svelte @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/test/validator/samples/logic-block-in-textarea/errors.json b/test/validator/samples/logic-block-in-textarea/errors.json new file mode 100644 index 0000000000..b1d73d4903 --- /dev/null +++ b/test/validator/samples/logic-block-in-textarea/errors.json @@ -0,0 +1,9 @@ +[ + { + "code": "invalid-logic-block-placement", + "message": "{#each} logic block cannot be inside \ No newline at end of file From 2b7393885724ed158dd3ce1b9e53b9737a5f3c3e Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Fri, 7 Oct 2022 08:25:04 +0800 Subject: [PATCH 02/27] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d12212be87..6ca984af7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ * Better error message when specifying an invalid value for `` ([#7550](https://github.com/sveltejs/svelte/issues/7550)) * Fix to call component unmount if a component is mounted and then immediately unmounted ([#7817](https://github.com/sveltejs/svelte/issues/7817)) * Fix false positive warnings about props binding in dev mode ([#4457](https://github.com/sveltejs/svelte/issues/4457)) +* Better error message when using logic blocks or tags in invalid place ([#7552](https://github.com/sveltejs/svelte/issues/7552)) ## 3.50.1 From 220325cd9feea12f9a9bc5eb8df5120f856b1bc9 Mon Sep 17 00:00:00 2001 From: Samuel Stroschein <35429197+samuelstroschein@users.noreply.github.com> Date: Fri, 7 Oct 2022 11:11:46 +0200 Subject: [PATCH 03/27] [docs] add inline documentation to svelte runtime functions (#7846) * add documentation * add links do docs --- src/runtime/internal/lifecycle.ts | 69 +++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/src/runtime/internal/lifecycle.ts b/src/runtime/internal/lifecycle.ts index fbbeca9a67..e75bbdc501 100644 --- a/src/runtime/internal/lifecycle.ts +++ b/src/runtime/internal/lifecycle.ts @@ -11,18 +11,47 @@ export function get_current_component() { return current_component; } +/** + * Schedules a callback to run immediately before the component is updated after any state change. + * + * The first time the callback runs will be before the initial `onMount` + * + * https://svelte.dev/docs#run-time-svelte-beforeupdate + */ export function beforeUpdate(fn: () => any) { get_current_component().$$.before_update.push(fn); } +/** + * The `onMount` function schedules a callback to run as soon as the component has been mounted to the DOM. + * It must be called during the component's initialisation (but doesn't need to live *inside* the component; + * it can be called from an external module). + * + * `onMount` does not run inside a [server-side component](/docs#run-time-server-side-component-api). + * + * https://svelte.dev/docs#run-time-svelte-onmount + */ export function onMount(fn: () => any) { get_current_component().$$.on_mount.push(fn); } +/** + * Schedules a callback to run immediately after the component has been updated. + * + * The first time the callback runs will be after the initial `onMount` + */ export function afterUpdate(fn: () => any) { get_current_component().$$.after_update.push(fn); } +/** + * Schedules a callback to run immediately before the component is unmounted. + * + * Out of `onMount`, `beforeUpdate`, `afterUpdate` and `onDestroy`, this is the + * only one that runs inside a server-side component. + * + * https://svelte.dev/docs#run-time-svelte-ondestroy + */ export function onDestroy(fn: () => any) { get_current_component().$$.on_destroy.push(fn); } @@ -31,6 +60,18 @@ export interface DispatchOptions { cancelable?: boolean; } +/** + * Creates an event dispatcher that can be used to dispatch [component events](/docs#template-syntax-component-directives-on-eventname). + * Event dispatchers are functions that can take two arguments: `name` and `detail`. + * + * Component events created with `createEventDispatcher` create a + * [CustomEvent](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent). + * These events do not [bubble](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#Event_bubbling_and_capture). + * The `detail` argument corresponds to the [CustomEvent.detail](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/detail) + * property and can contain any type of data. + * + * https://svelte.dev/docs#run-time-svelte-createeventdispatcher + */ export function createEventDispatcher(): < EventKey extends Extract >( @@ -57,19 +98,47 @@ export function createEventDispatcher(): < }; } +/** + * Associates an arbitrary `context` object with the current component and the specified `key` + * and returns that object. The context is then available to children of the component + * (including slotted content) with `getContext`. + * + * Like lifecycle functions, this must be called during component initialisation. + * + * https://svelte.dev/docs#run-time-svelte-setcontext + */ export function setContext(key, context: T): T { get_current_component().$$.context.set(key, context); return context; } +/** + * Retrieves the context that belongs to the closest parent component with the specified `key`. + * Must be called during component initialisation. + * + * https://svelte.dev/docs#run-time-svelte-getcontext + */ export function getContext(key): T { return get_current_component().$$.context.get(key); } +/** + * Retrieves the whole context map that belongs to the closest parent component. + * Must be called during component initialisation. Useful, for example, if you + * programmatically create a component and want to pass the existing context to it. + * + * https://svelte.dev/docs#run-time-svelte-getallcontexts + */ export function getAllContexts = Map>(): T { return get_current_component().$$.context; } +/** + * Checks whether a given `key` has been set in the context of a parent component. + * Must be called during component initialisation. + * + * https://svelte.dev/docs#run-time-svelte-hascontext + */ export function hasContext(key): boolean { return get_current_component().$$.context.has(key); } From ff2759e1437559669bd032abaff6920c2fc46570 Mon Sep 17 00:00:00 2001 From: Geoff Rich <4992896+geoffrich@users.noreply.github.com> Date: Fri, 7 Oct 2022 16:25:19 -0700 Subject: [PATCH 04/27] [docs] fix typo in getting started (#7918) --- site/content/docs/01-getting-started.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/site/content/docs/01-getting-started.md b/site/content/docs/01-getting-started.md index 8ae5b1fb9c..e8aae80f23 100644 --- a/site/content/docs/01-getting-started.md +++ b/site/content/docs/01-getting-started.md @@ -7,6 +7,7 @@ title: Getting started To try Svelte in an interactive online environment you can try [the REPL](https://svelte.dev/repl) or [StackBlitz](https://node.new/svelte). To create a project locally, run: + ``` npm create vite@latest myapp -- --template svelte cd myapp @@ -16,6 +17,6 @@ npm run dev Or use [SvelteKit](https://kit.svelte.dev/), the official application framework from the Svelte team (currently in beta). -See the SvelteSociety website run by the Svelte community for a list of integrations with various [tooling and editots](https://sveltesociety.dev/tools). +The Svelte team maintains a [VS Code extension](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode) and the Svelte community has created a list of integrations with various other [tooling and editors](https://sveltesociety.dev/tools). If you're having trouble, get help on [Discord](https://svelte.dev/chat) or [StackOverflow](https://stackoverflow.com/questions/tagged/svelte). From 1c659193a0b1fdf6e472be182fb600a430784d20 Mon Sep 17 00:00:00 2001 From: Ben McCann <322311+benmccann@users.noreply.github.com> Date: Fri, 7 Oct 2022 16:34:31 -0700 Subject: [PATCH 05/27] [docs] update SvelteKit status (#7919) --- site/content/docs/01-getting-started.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/content/docs/01-getting-started.md b/site/content/docs/01-getting-started.md index e8aae80f23..48cdbcc231 100644 --- a/site/content/docs/01-getting-started.md +++ b/site/content/docs/01-getting-started.md @@ -15,7 +15,7 @@ npm install npm run dev ``` -Or use [SvelteKit](https://kit.svelte.dev/), the official application framework from the Svelte team (currently in beta). +Or use [SvelteKit](https://kit.svelte.dev/), the official application framework from the Svelte team (currently in release candidate status). The Svelte team maintains a [VS Code extension](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode) and the Svelte community has created a list of integrations with various other [tooling and editors](https://sveltesociety.dev/tools). From 6ac7038e47c38221f16f0f73af93c9024ff2a18c Mon Sep 17 00:00:00 2001 From: Conduitry Date: Mon, 10 Oct 2022 13:15:45 -0400 Subject: [PATCH 06/27] -> v3.51.0 --- CHANGELOG.md | 34 +++++++++++++++++----------------- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ca984af7d..cbf22201f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,26 +1,26 @@ # Svelte changelog -## Unreleased +## 3.51.0 -* Fix hydration issue with using `{@html}` and components in `svelte:head` ([#4533](https://github.com/sveltejs/svelte/issues/4533), [#6463](https://github.com/sveltejs/svelte/issues/6463), [#7444](https://github.com/sveltejs/svelte/issues/7444)) -* Warn instead of throwing error if `` is void tag ([#7566](https://github.com/sveltejs/svelte/issues/7566)) -* Treat `inert` as boolean attribute ([#7785](https://github.com/sveltejs/svelte/pull/7785)) -* Supporting scoped style for `` ([#7443](https://github.com/sveltejs/svelte/issues/7443)) -* Supports SVG elements with ``([#7613](https://github.com/sveltejs/svelte/issues/7613)) -* Warn user when binding on a `{...rest}` object in `{#each}` block ([#6860](https://github.com/sveltejs/svelte/issues/6860)) -* Supports `--style-props` for `` ([#7461](https://github.com/sveltejs/svelte/issues/7461)) -* Supports `--style-props` for SVG components ([#7808](https://github.com/sveltejs/svelte/issues/7808)) * Add a11y warnings: - * `a11y-no-noninteractive-tabindex`: check for tabindex on non-interactive elements ([#6693](https://github.com/sveltejs/svelte/pull/6693)) * `a11y-click-events-have-key-events`: check if click event is accompanied by key events ([#5073](https://github.com/sveltejs/svelte/pull/5073)) -* `a11y-role-has-required-aria-props` do not warn when elements matched their semantic role ([#7838](https://github.com/sveltejs/svelte/pull/7838)) -* Supports custom element in `` ([#7766](https://github.com/sveltejs/svelte/pull/7766)) -* Improve performance of custom element data setting in `` ([#7869](https://github.com/sveltejs/svelte/pull/7869)) + * `a11y-no-noninteractive-tabindex`: check for tabindex on non-interactive elements ([#6693](https://github.com/sveltejs/svelte/pull/6693)) +* Warn when two-way binding to `{...rest}` object in `{#each}` block ([#6860](https://github.com/sveltejs/svelte/issues/6860)) +* Support `--style-props` on `` ([#7461](https://github.com/sveltejs/svelte/issues/7461)) * Supports nullish values for component event handlers ([#7568](https://github.com/sveltejs/svelte/issues/7568)) -* Better error message when specifying an invalid value for `` ([#7550](https://github.com/sveltejs/svelte/issues/7550)) -* Fix to call component unmount if a component is mounted and then immediately unmounted ([#7817](https://github.com/sveltejs/svelte/issues/7817)) -* Fix false positive warnings about props binding in dev mode ([#4457](https://github.com/sveltejs/svelte/issues/4457)) -* Better error message when using logic blocks or tags in invalid place ([#7552](https://github.com/sveltejs/svelte/issues/7552)) +* Supports SVG elements with ``([#7613](https://github.com/sveltejs/svelte/issues/7613)) +* Treat `inert` as boolean attribute ([#7785](https://github.com/sveltejs/svelte/pull/7785)) +* Support `--style-props` for SVG components ([#7808](https://github.com/sveltejs/svelte/issues/7808)) +* Fix false positive dev warnings about unset props when they are bound ([#4457](https://github.com/sveltejs/svelte/issues/4457)) +* Fix hydration with `{@html}` and components in `` ([#4533](https://github.com/sveltejs/svelte/issues/4533), [#6463](https://github.com/sveltejs/svelte/issues/6463), [#7444](https://github.com/sveltejs/svelte/issues/7444)) +* Support scoped style for `` ([#7443](https://github.com/sveltejs/svelte/issues/7443)) +* Improve error message for invalid value for `` ([#7550](https://github.com/sveltejs/svelte/issues/7550)) +* Improve error message when using logic blocks or tags at invalid location ([#7552](https://github.com/sveltejs/svelte/issues/7552)) +* Warn instead of throwing error if `` is a void tag ([#7566](https://github.com/sveltejs/svelte/issues/7566)) +* Supports custom elements in `` ([#7733](https://github.com/sveltejs/svelte/issues/7733)) +* Fix calling component unmount if a component is mounted and then immediately unmounted ([#7817](https://github.com/sveltejs/svelte/issues/7817)) +* Do not generate `a11y-role-has-required-aria-props` warning when elements match their semantic role ([#7837](https://github.com/sveltejs/svelte/issues/7837)) +* Improve performance of custom element data setting in `` ([#7869](https://github.com/sveltejs/svelte/pull/7869)) ## 3.50.1 diff --git a/package-lock.json b/package-lock.json index 23ccc6b66f..dec2c8ef0e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "3.50.1", + "version": "3.51.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 836fa33267..bf6b9e7754 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "3.50.1", + "version": "3.51.0", "description": "Cybernetically enhanced web apps", "module": "index.mjs", "main": "index", From 26a428972b7865d3871e05368df5e7b47bbf7e5b Mon Sep 17 00:00:00 2001 From: metonym Date: Thu, 13 Oct 2022 05:49:00 -0700 Subject: [PATCH 07/27] [fix] "not interactive" -> "noninteractive" (#7930) --- site/content/docs/06-accessibility-warnings.md | 2 +- src/compiler/compile/compiler_warnings.ts | 2 +- .../samples/a11y-no-nointeractive-tabindex/warnings.json | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/site/content/docs/06-accessibility-warnings.md b/site/content/docs/06-accessibility-warnings.md index c82ef21fcb..8be8eb888c 100644 --- a/site/content/docs/06-accessibility-warnings.md +++ b/site/content/docs/06-accessibility-warnings.md @@ -268,7 +268,7 @@ Some HTML elements have default ARIA roles. Giving these elements an ARIA role t Tab key navigation should be limited to elements on the page that can be interacted with. ```sv - +
``` diff --git a/src/compiler/compile/compiler_warnings.ts b/src/compiler/compile/compiler_warnings.ts index f194090870..3a11e04bd1 100644 --- a/src/compiler/compile/compiler_warnings.ts +++ b/src/compiler/compile/compiler_warnings.ts @@ -185,7 +185,7 @@ export default { }), a11y_no_noninteractive_tabindex: { code: 'a11y-no-noninteractive-tabindex', - message: 'A11y: not interactive element cannot have positive tabIndex value' + message: 'A11y: noninteractive element cannot have positive tabIndex value' }, redundant_event_modifier_for_touch: { code: 'redundant-event-modifier', diff --git a/test/validator/samples/a11y-no-nointeractive-tabindex/warnings.json b/test/validator/samples/a11y-no-nointeractive-tabindex/warnings.json index 97b8ccd9b3..740d9b346c 100644 --- a/test/validator/samples/a11y-no-nointeractive-tabindex/warnings.json +++ b/test/validator/samples/a11y-no-nointeractive-tabindex/warnings.json @@ -6,7 +6,7 @@ "column": 20, "line": 11 }, - "message": "A11y: not interactive element cannot have positive tabIndex value", + "message": "A11y: noninteractive element cannot have positive tabIndex value", "pos": 221, "start": { "character": 221, @@ -21,7 +21,7 @@ "column": 35, "line": 12 }, - "message": "A11y: not interactive element cannot have positive tabIndex value", + "message": "A11y: noninteractive element cannot have positive tabIndex value", "pos": 242, "start": { "character": 242, @@ -36,7 +36,7 @@ "column": 24, "line": 13 }, - "message": "A11y: not interactive element cannot have positive tabIndex value", + "message": "A11y: noninteractive element cannot have positive tabIndex value", "pos": 278, "start": { "character": 278, @@ -51,7 +51,7 @@ "column": 26, "line": 14 }, - "message": "A11y: not interactive element cannot have positive tabIndex value", + "message": "A11y: noninteractive element cannot have positive tabIndex value", "pos": 303, "start": { "character": 303, From 0eba57113be4185836a76ba894cbc29f366da252 Mon Sep 17 00:00:00 2001 From: Hofer Ivan Date: Thu, 13 Oct 2022 14:54:30 +0200 Subject: [PATCH 08/27] [chore]: store regexp as variable instead of defining it inline (#7716) * store regexp as variable instead of defining it inline * fix naming of `regex_quoted_value` * some more variables * optimize `.replace() calls * restore formatting changes * optimize `parser.*` calls * small refactor * optimize `.test() calls * rename some variables * fix tests * rename pattern variables * extract common regexes into `patters.ts` * rename variables to use snake_case * fix trim --- src/compiler/compile/Component.ts | 22 +++++++----- src/compiler/compile/css/Selector.ts | 15 +++++--- src/compiler/compile/css/Stylesheet.ts | 9 +++-- src/compiler/compile/index.ts | 7 ++-- src/compiler/compile/nodes/Binding.ts | 4 +-- src/compiler/compile/nodes/Element.ts | 26 +++++++++----- src/compiler/compile/nodes/EventHandler.ts | 6 ++-- src/compiler/compile/nodes/Head.ts | 3 +- src/compiler/compile/nodes/InlineComponent.ts | 7 ++-- src/compiler/compile/nodes/Text.ts | 7 ++-- .../compile/nodes/shared/AbstractBlock.ts | 4 ++- .../compile/nodes/shared/Expression.ts | 4 ++- src/compiler/compile/render_dom/Block.ts | 3 +- src/compiler/compile/render_dom/index.ts | 3 +- .../render_dom/wrappers/Element/Attribute.ts | 17 +++++---- .../wrappers/Element/StyleAttribute.ts | 13 ++++--- .../render_dom/wrappers/Element/index.ts | 36 ++++++++++++------- .../compile/render_dom/wrappers/Fragment.ts | 3 +- .../wrappers/InlineComponent/index.ts | 4 ++- .../compile/render_dom/wrappers/Text.ts | 4 ++- .../render_dom/wrappers/shared/add_actions.ts | 4 ++- .../shared/create_debugging_comment.ts | 4 ++- .../compile/render_ssr/handlers/Element.ts | 6 ++-- .../handlers/shared/get_attribute_value.ts | 3 +- .../utils/remove_whitespace_children.ts | 3 +- .../compile/utils/get_name_from_filename.ts | 19 ++++++---- src/compiler/compile/utils/hash.ts | 5 ++- src/compiler/compile/utils/stringify.ts | 8 +++-- src/compiler/parse/index.ts | 14 ++++---- src/compiler/parse/read/context.ts | 3 +- src/compiler/parse/read/expression.ts | 4 +-- src/compiler/parse/read/script.ts | 9 +++-- src/compiler/parse/read/style.ts | 6 ++-- src/compiler/parse/state/mustache.ts | 30 ++++++++-------- src/compiler/parse/state/tag.ts | 26 +++++++++----- src/compiler/preprocess/index.ts | 22 +++++++----- src/compiler/utils/extract_svelte_ignore.ts | 7 ++-- src/compiler/utils/get_code_frame.ts | 4 ++- src/compiler/utils/mapped_code.ts | 10 ++++-- src/compiler/utils/names.ts | 12 ++++--- src/compiler/utils/patterns.ts | 28 ++++++++++++--- src/compiler/utils/trim.ts | 6 ++-- 42 files changed, 283 insertions(+), 147 deletions(-) diff --git a/src/compiler/compile/Component.ts b/src/compiler/compile/Component.ts index 2f8874de7a..1bbaac3ff4 100644 --- a/src/compiler/compile/Component.ts +++ b/src/compiler/compile/Component.ts @@ -47,6 +47,10 @@ interface ComponentOptions { preserveWhitespace?: boolean; } +const regex_leading_directory_separator = /^[/\\]/; +const regex_starts_with_term_export = /^Export/; +const regex_contains_term_function = /Function/; + export default class Component { stats: Stats; warnings: Warning[]; @@ -136,7 +140,7 @@ export default class Component { (typeof process !== 'undefined' ? compile_options.filename .replace(process.cwd(), '') - .replace(/^[/\\]/, '') + .replace(regex_leading_directory_separator, '') : compile_options.filename); this.locate = getLocator(this.source, { offsetLine: 1 }); @@ -638,7 +642,7 @@ export default class Component { body.splice(i, 1); } - if (/^Export/.test(node.type)) { + if (regex_starts_with_term_export.test(node.type)) { const replacement = this.extract_exports(node, true); if (replacement) { body[i] = replacement; @@ -795,7 +799,7 @@ export default class Component { return this.skip(); } - if (/^Export/.test(node.type)) { + if (regex_starts_with_term_export.test(node.type)) { const replacement = component.extract_exports(node); if (replacement) { this.replace(replacement); @@ -918,7 +922,7 @@ export default class Component { } if (name[1] !== '$' && scope.has(name.slice(1)) && scope.find_owner(name.slice(1)) !== this.instance_scope) { - if (!((/Function/.test(parent.type) && prop === 'params') || (parent.type === 'VariableDeclarator' && prop === 'id'))) { + if (!((regex_contains_term_function.test(parent.type) && prop === 'params') || (parent.type === 'VariableDeclarator' && prop === 'id'))) { return this.error(node as any, compiler_errors.contextual_store); } } @@ -965,7 +969,7 @@ export default class Component { walk(this.ast.instance.content, { enter(node: Node) { - if (/Function/.test(node.type)) { + if (regex_contains_term_function.test(node.type)) { return this.skip(); } @@ -1089,7 +1093,7 @@ export default class Component { this.replace(b` ${node.declarations.length ? node : null} - ${ props.length > 0 && b`let { ${ props } } = $$props;`} + ${ props.length > 0 && b`let { ${props} } = $$props;`} ${inserts} ` as any); return this.skip(); @@ -1460,6 +1464,8 @@ export default class Component { } } +const regex_valid_tag_name = /^[a-zA-Z][a-zA-Z0-9]*-[a-zA-Z0-9-]+$/; + function process_component_options(component: Component, nodes) { const component_options: ComponentOptions = { immutable: component.compile_options.immutable || false, @@ -1473,7 +1479,7 @@ function process_component_options(component: Component, nodes) { const node = nodes.find(node => node.name === 'svelte:options'); - function get_value(attribute, {code, message}) { + function get_value(attribute, { code, message }) { const { value } = attribute; const chunk = value[0]; @@ -1505,7 +1511,7 @@ function process_component_options(component: Component, nodes) { return component.error(attribute, compiler_errors.invalid_tag_attribute); } - if (tag && !/^[a-zA-Z][a-zA-Z0-9]*-[a-zA-Z0-9-]+$/.test(tag)) { + if (tag && !regex_valid_tag_name.test(tag)) { return component.error(attribute, compiler_errors.invalid_tag_property); } diff --git a/src/compiler/compile/css/Selector.ts b/src/compiler/compile/css/Selector.ts index 17302c4abd..28cf5ba6af 100644 --- a/src/compiler/compile/css/Selector.ts +++ b/src/compiler/compile/css/Selector.ts @@ -9,6 +9,7 @@ import EachBlock from '../nodes/EachBlock'; import IfBlock from '../nodes/IfBlock'; import AwaitBlock from '../nodes/AwaitBlock'; import compiler_errors from '../compiler_errors'; +import { regex_starts_with_whitespace, regex_ends_with_whitespace } from '../../utils/patterns'; enum BlockAppliesToNode { NotPossible, @@ -25,6 +26,8 @@ const whitelist_attribute_selector = new Map([ ['dialog', new Set(['open'])] ]); +const regex_is_single_css_selector = /[^\\],(?!([^([]+[^\\]|[^([\\])[)\]])/; + export default class Selector { node: CssNode; stylesheet: Stylesheet; @@ -157,7 +160,7 @@ export default class Selector { for (const block of this.blocks) { for (const selector of block.selectors) { if (selector.type === 'PseudoClassSelector' && selector.name === 'global') { - if (/[^\\],(?!([^([]+[^\\]|[^([\\])[)\]])/.test(selector.children[0].value)) { + if (regex_is_single_css_selector.test(selector.children[0].value)) { component.error(selector, compiler_errors.css_invalid_global_selector); } } @@ -281,12 +284,14 @@ function apply_selector(blocks: Block[], node: Element, to_encapsulate: Array<{ return true; } +const regex_backslash_and_following_character = /\\(.)/g; + function block_might_apply_to_node(block: Block, node: Element): BlockAppliesToNode { let i = block.selectors.length; while (i--) { const selector = block.selectors[i]; - const name = typeof selector.name === 'string' && selector.name.replace(/\\(.)/g, '$1'); + const name = typeof selector.name === 'string' && selector.name.replace(regex_backslash_and_following_character, '$1'); if (selector.type === 'PseudoClassSelector' && (name === 'host' || name === 'root')) { return BlockAppliesToNode.NotPossible; @@ -371,7 +376,7 @@ function attribute_matches(node: CssNode, name: string, expected_value: string, const start_with_space = []; const remaining = []; current_possible_values.forEach((current_possible_value: string) => { - if (/^\s/.test(current_possible_value)) { + if (regex_starts_with_whitespace.test(current_possible_value)) { start_with_space.push(current_possible_value); } else { remaining.push(current_possible_value); @@ -392,7 +397,7 @@ function attribute_matches(node: CssNode, name: string, expected_value: string, prev_values = combined; start_with_space.forEach((value: string) => { - if (/\s$/.test(value)) { + if (regex_ends_with_whitespace.test(value)) { possible_values.add(value); } else { prev_values.push(value); @@ -406,7 +411,7 @@ function attribute_matches(node: CssNode, name: string, expected_value: string, } current_possible_values.forEach((current_possible_value: string) => { - if (/\s$/.test(current_possible_value)) { + if (regex_ends_with_whitespace.test(current_possible_value)) { possible_values.add(current_possible_value); } else { prev_values.push(current_possible_value); diff --git a/src/compiler/compile/css/Stylesheet.ts b/src/compiler/compile/css/Stylesheet.ts index 1a9ea7feeb..9a3cbe9d13 100644 --- a/src/compiler/compile/css/Stylesheet.ts +++ b/src/compiler/compile/css/Stylesheet.ts @@ -9,9 +9,12 @@ import hash from '../utils/hash'; import compiler_warnings from '../compiler_warnings'; import { extract_ignores_above_position } from '../../utils/extract_svelte_ignore'; import { push_array } from '../../utils/push_array'; +import { regex_only_whitespaces, regex_whitespace } from '../../utils/patterns'; + +const regex_css_browser_prefix = /^-((webkit)|(moz)|(o)|(ms))-/; function remove_css_prefix(name: string): string { - return name.replace(/^-((webkit)|(moz)|(o)|(ms))-/, ''); + return name.replace(regex_css_browser_prefix, ''); } const is_keyframes_node = (node: CssNode) => @@ -147,10 +150,10 @@ class Declaration { // Don't minify whitespace in custom properties, since some browsers (Chromium < 99) // treat --foo: ; and --foo:; differently - if (first.type === 'Raw' && /^\s+$/.test(first.value)) return; + if (first.type === 'Raw' && regex_only_whitespaces.test(first.value)) return; let start = first.start; - while (/\s/.test(code.original[start])) start += 1; + while (regex_whitespace.test(code.original[start])) start += 1; if (start - c > 1) { code.overwrite(c, start, ':'); diff --git a/src/compiler/compile/index.ts b/src/compiler/compile/index.ts index afe9c56cf4..a5edc3a6f7 100644 --- a/src/compiler/compile/index.ts +++ b/src/compiler/compile/index.ts @@ -35,6 +35,9 @@ const valid_options = [ 'cssHash' ]; +const regex_valid_identifier = /^[a-zA-Z_$][a-zA-Z_$0-9]*$/; +const regex_starts_with_lowercase_character = /^[a-z]/; + function validate_options(options: CompileOptions, warnings: Warning[]) { const { name, filename, loopGuardTimeout, dev, namespace } = options; @@ -48,11 +51,11 @@ function validate_options(options: CompileOptions, warnings: Warning[]) { } }); - if (name && !/^[a-zA-Z_$][a-zA-Z_$0-9]*$/.test(name)) { + if (name && !regex_valid_identifier.test(name)) { throw new Error(`options.name must be a valid identifier (got '${name}')`); } - if (name && /^[a-z]/.test(name)) { + if (name && regex_starts_with_lowercase_character.test(name)) { const message = 'options.name should be capitalised'; warnings.push({ code: 'options-lowercase-name', diff --git a/src/compiler/compile/nodes/Binding.ts b/src/compiler/compile/nodes/Binding.ts index 594490a5fb..f826df4828 100644 --- a/src/compiler/compile/nodes/Binding.ts +++ b/src/compiler/compile/nodes/Binding.ts @@ -3,7 +3,7 @@ import get_object from '../utils/get_object'; import Expression from './shared/Expression'; import Component from '../Component'; import TemplateScope from './shared/TemplateScope'; -import {dimensions} from '../../utils/patterns'; +import { regex_dimensions } from '../../utils/patterns'; import { Node as ESTreeNode } from 'estree'; import { TemplateNode } from '../../interfaces'; import Element from './Element'; @@ -88,7 +88,7 @@ export default class Binding extends Node { const type = parent.get_static_attribute_value('type'); this.is_readonly = - dimensions.test(this.name) || + regex_dimensions.test(this.name) || (isElement(parent) && ((parent.is_media_node() && read_only_media_attributes.has(this.name)) || (parent.name === 'input' && type === 'file')) /* TODO others? */); diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index 79f2800437..d5fad18f54 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -11,7 +11,7 @@ import StyleDirective from './StyleDirective'; import Text from './Text'; import { namespaces } from '../../utils/namespaces'; import map_children from './shared/map_children'; -import { dimensions, start_newline } from '../../utils/patterns'; +import { regex_dimensions, regex_starts_with_newline, regex_non_whitespace_character } from '../../utils/patterns'; import fuzzymatch from '../../utils/fuzzymatch'; import list from '../../utils/list'; import Let from './Let'; @@ -203,6 +203,10 @@ function is_valid_aria_attribute_value(schema: ARIAPropertyDefinition, value: st } } +const regex_any_repeated_whitespaces = /[\s]+/g; +const regex_heading_tags = /^h[1-6]$/; +const regex_illegal_attribute_character = /(^[0-9-.])|[\^$@%&#?!|()[\]{}^*+~;]/; + export default class Element extends Node { type: 'Element'; name: string; @@ -253,7 +257,7 @@ export default class Element extends Node { // places if there's another newline afterwards. // see https://html.spec.whatwg.org/multipage/syntax.html#element-restrictions // see https://html.spec.whatwg.org/multipage/grouping-content.html#the-pre-element - first.data = first.data.replace(start_newline, ''); + first.data = first.data.replace(regex_starts_with_newline, ''); } } @@ -398,7 +402,7 @@ export default class Element extends Node { // Errors - if (/(^[0-9-.])|[\^$@%&#?!|()[\]{}^*+~;]/.test(name)) { + if (regex_illegal_attribute_character.test(name)) { return component.error(attribute, compiler_errors.illegal_attribute(name)); } @@ -464,7 +468,7 @@ export default class Element extends Node { component.warn(attribute, compiler_warnings.a11y_unknown_aria_attribute(type, match)); } - if (name === 'aria-hidden' && /^h[1-6]$/.test(this.name)) { + if (name === 'aria-hidden' && regex_heading_tags.test(this.name)) { component.warn(attribute, compiler_warnings.a11y_hidden(this.name)); } @@ -729,7 +733,7 @@ export default class Element extends Node { if (this.name === 'figure') { const children = this.children.filter(node => { if (node.type === 'Comment') return false; - if (node.type === 'Text') return /\S/.test(node.data); + if (node.type === 'Text') return regex_non_whitespace_character.test(node.data); return true; }); @@ -861,7 +865,7 @@ export default class Element extends Node { if (this.name !== 'video') { return component.error(binding, compiler_errors.invalid_binding_element_with('