From 31e5f8b5de24e2e058cb1a70467c0092e422ee5d Mon Sep 17 00:00:00 2001 From: Daniel Sandoval Date: Fri, 1 Jul 2022 09:17:52 -0600 Subject: [PATCH 001/168] [docs] "What's new in Svelte" July newsletter (#7637) --- ...022-07-01-whats-new-in-svelte-july-2022.md | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 site/content/blog/2022-07-01-whats-new-in-svelte-july-2022.md diff --git a/site/content/blog/2022-07-01-whats-new-in-svelte-july-2022.md b/site/content/blog/2022-07-01-whats-new-in-svelte-july-2022.md new file mode 100644 index 0000000000..e8c76a0857 --- /dev/null +++ b/site/content/blog/2022-07-01-whats-new-in-svelte-july-2022.md @@ -0,0 +1,98 @@ +--- +title: "What's new in Svelte: July 2022" +description: "Faster SSR, language tools improvements and a new paid contributor!" +author: Daniel Sandoval +authorURL: https://desandoval.net +--- + +From faster SSR to support for Vitest and Storybook in SvelteKit, there's a lot to cover in this month's newsletter... + +So let's dive in! + +## OpenCollective funding drives Svelte forward + +Svelte supporters have donated approximately $80,000 to [the project on OpenCollective](https://opencollective.com/svelte). We're happy to share that the funds are being drawn on to move Svelte forward in a meaningful way. **[@gtm-nayan](https://github.com/gtm-nayan)** has begun triaging and fixing SvelteKit issues this past month as a paid contributor to the project to help us get SvelteKit to a 1.0 level of stability! @gtm-nayan has been an active member of the Svelte community for quite some time and is well known for writing the bot that helps keep our Discord server running. We're happy that this funding has allowed Svelte to get much more of his time. + +We will also be utilizing OpenCollective funds to allow Svelte core maintainers to attend [Svelte Summit](https://www.sveltesummit.com/) in person this fall. Thanks to everyone who has donated so far! + +## What's new in Svelte & Language Tools +- [learn.svelte.dev](https://learn.svelte.dev/) is a new way to learn Svelte and SvelteKit from the ground up that is currently in development +- Faster SSR is coming in the next Svelte release. A PR two years in the making, resulting in up to 3x faster rendering in some benchmarking tests! ([PR](https://github.com/sveltejs/svelte/pull/5701)) +- "Find File References" ([0.14.28](https://github.com/sveltejs/language-tools/releases/tag/language-server-0.14.28)) and "Find Component References" ([0.14.29](https://github.com/sveltejs/language-tools/releases/tag/language-server-0.14.29)) in the latest versions of the Svelte extension shows where Svelte files and components have been imported and used ([Demo](https://twitter.com/dummdidumm_/status/1532459709604716544/photo/1)) +- The Svelte extension now supports CSS path completion ([0.14.29](https://github.com/sveltejs/language-tools/releases/tag/language-server-0.14.29)) + + +## What's new in SvelteKit +- Introduced `@sveltejs/kit/experimental/vite` which allows SvelteKit to interoperate with other tools in the Vite ecosystem like Vitest and Storybook ([#5094](https://github.com/sveltejs/kit/pull/5094)). Please [leave feedback](https://github.com/sveltejs/kit/issues/5184) as to whether the feature works and is helpful as we consider taking it out of experimental and making `vite.config.js` required for all users +- Streaming in endpoints is now supported ([#3419](https://github.com/sveltejs/kit/issues/3419)). This was enabled by switching to the Undici `fetch` implementation ([#5117](https://github.com/sveltejs/kit/pull/5117)) +- Static assets can now be symlinked in development environments ([#5089](https://github.com/sveltejs/kit/pull/5089)) +- `server` and `prod` environment variables are now available as a correlary to `browser` and `dev` ([#5251](https://github.com/sveltejs/kit/pull/5251)) + +--- + +## Community Showcase + +**Apps & Sites built with Svelte** +- [Virtual Maker](https://www.virtualmaker.net/) lets you make interactive 3D and VR scenes in your browser +- [Apple Beta Music](https://www.reddit.com/r/sveltejs/comments/v7ic2s/apple_beta_music_uses_svelte/) appears to have been written in some combination of Svelte and web components +- [Itatiaia](https://www.itatiaia.com.br/), the largest radio station in the country of Brazil just relaunched its news portal in SvelteKit +- [Pronauns](https://www.pronauns.com) helps you learn pronunciation online with IPA to speak better and sound more native +- [Immich](https://www.immich.app/) is an open source, high performance self-hosted backup solution for videos and photos on your mobile phone +- [Pendek](https://github.com/leovoon/link-shortener) is a link shortener built with SvelteKit, Prisma and PlanetScale +- [Grunfy](https://grunfy.com/tools) is a set of guitar tools - recently migrated to SvelteKit +- [Radiant: The Future of Radio](https://play.google.com/store/apps/details?id=co.broadcastapp.Radiant) is a personal radio station app built with Svelte and Capacitor +- [Imperfect Reminders](https://imperfectreminders.mildlyupset.com/) is a todo list for things that are only sort of time sensitive +- [Periodic Table](https://github.com/janosh/periodic-table) is a dynamic Periodic Table component written in Svelte +- [Svelvet](https://github.com/open-source-labs/Svelvet) is a lightweight Svelte component library for building interactive node-based diagrams +- [publint](https://github.com/bluwy/publint) lints for packaging errors to ensure compatibility across environments +- [Playlistr](https://github.com/alextana/spotify-playlist-creator) helps manage and create Spotify playlists +- [Geoff Rich's page transitions demo](https://twitter.com/geoffrich_/status/1534980702785003520) shows how SvelteKit's `beforeNavigate`/`afterNavigate` hooks can make smooth document transitions in the latest Chrome Canary +- [Menger Sponge](https://twitter.com/a_warnes/status/1536215896078811137) is a fractal built with Threlte + +Want to contribute to a site using the latest SvelteKit features? [Help build the Svelte Society site](https://github.com/svelte-society/sveltesociety.dev/issues)! + + +**Learning Resources** + +_Starring the Svelte team_ +- [Svelte Origins: A JavaScript Documentary](https://www.youtube.com/watch?v=kMlkCYL9qo0) by OfferZen Origins +- [Full Stack Documentation (announcing learn.svelte.dev)](https://portal.gitnation.org/contents/full-stack-documentation) by Rich Harris @ JSNation 2022 +- [All About the Sirens](https://www.svelteradio.com/episodes/all-about-the-sirens) by Svelte Radio + +_To Watch_ +- [SvelteKit Page Endpoints](https://www.youtube.com/watch?v=yQRf2wmTu5w), [Named Layouts](https://www.youtube.com/watch?v=UHX9TJ0BxZY) and [Passing data from page component to layout component with $page.stuff](https://www.youtube.com/watch?v=CXaCstU5pcw) by lihautan +- [🍞 & 🧈: Magically load data with SvelteKit Endpoints](https://www.youtube.com/watch?v=f6prqYlbTE4) by Johnny Magrippis +- [Svelte for React developers](https://www.youtube.com/watch?v=7tsrwrx5HtQ) by frontendtier +- [Learn Svelte JS || Javascript Compiler for Building Front end Applications](https://www.youtube.com/watch?v=1rKRarJJFrY&list=PLIGDNOJWiL1-7zCgdR7MKuho-tPC6Ra6C&index=1) by Code with tsksharma +- [SvelteKit Authentication](https://www.youtube.com/watch?v=T935Ya4W5X0&list=PLA9WiRZ-IS_zKrDzhOhV5RGKKTHNIyTDO&index=1) by Joy of Code +- [Svelte + websockets: Build a real-time Auction app](https://www.youtube.com/watch?v=CqgsWFrwQIU) by Evgeny Maksimov + +_To Read_ +- [Up-To-Date Analytics on a Static Website](https://paullj.github.io/posts/up-to-date-analytics-on-a-static-website) and [Fast, Lightweight Fuzzy Search using Fuse.js](https://paullj.github.io/posts/fast-lightweight-fuzzy-search-using-fuse.js) by paullj +- [Use SvelteKit as a handler in the ExpressJs project](https://chientrm.medium.com/use-sveltekit-as-a-handler-in-the-expressjs-project-15524b01128f) by Tran Chien +- [Creating a desktop application with Tauri and SvelteKit](https://github.com/Stijn-B/tauri-sveltekit-example) by Stijn-B +- [List of awesome Svelte stores](https://github.com/samuba/awesome-svelte-stores) by samuba +- [SvelteKit Content Security Policy: CSP for XSS Protection](https://rodneylab.com/sveltekit-content-security-policy/) by Rodney Lab +- [SvelteKit Hooks. Everything You Need To Know](https://kudadam.com/blog/understanding-sveltekit-hooks) by Lucretius K. Biah +- [3 tips for upgrading the performance of your Svelte stores](https://www.mathiaspicker.com/posts/3-tips-for-upgrading-the-performance-of-your-svelte-stores) by Mathias Picker + + +**Libraries, Tools & Components** +- [Svend3r](https://github.com/oslabs-beta/svend3r) is a plug and play D3 charting library for Svelte +- [Svelte Hover Draw SVG](https://github.com/davipon/svelte-hover-draw-svg) is a lightweight Svelte component to draw SVG on hover +- [Svelte French Toast](https://svelte-french-toast.com/) provides buttery smooth toast notifications that are lightweight, customizable, and beautiful by default +- [SVooltip](https://svooltip.vercel.app/) is a basic Svelte tooltip directive, powered by Floating UI +- [Svelte Brick Gallery](https://github.com/anotherempty/svelte-brick-gallery) is a masonry-like image gallery component for Svelte +- [use-vest](https://github.com/enyo/use-vest) is a Svelte action for Vest - a library that makes it easy to validate forms and show errors when necessary +- [Svelidate](https://github.com/svelidate/svelidate) is a simple and lightweight form validation library for Svelte with no dependencies +- [Svve11](https://github.com/oslabs-beta/Svve11) is an "accessbility-first" component library for Svelte +- [Slidy](https://github.com/Valexr/Slidy) is a simple, configurable & reusable carousel sliding action script with templates & some useful plugins +- [Svelte Component Snippets](https://marketplace.visualstudio.com/items?itemName=brysonbw.svelte-component-snippets) is a VS Code extension with access to common Svelte snippets +- [Svelte Confetti](https://github.com/Mitcheljager/svelte-confetti) adds a little bit of flair to your app with some confetti 🎊 + + +What did we miss? Let us know on [Reddit](https://www.reddit.com/r/sveltejs/) or [Discord](https://discord.com/invite/yy75DKs) to add your voice. + +Don't forget that you can also join us in-person at the Svelte Summit in Stockholm! Come join us for two days of awesome Svelte content! [Get your tickets now](https://www.sveltesummit.com/). + +See y'all next month! From 02f60fbebf7cdb036472d1aec8dc9d9f8215cd7a Mon Sep 17 00:00:00 2001 From: qinmu Date: Mon, 4 Jul 2022 09:45:39 +0800 Subject: [PATCH 002/168] [fix]destroy empty component (#7492) * fix: destroy non-fragment element such as empty components * fix: fragment property of Empty Component is set as true in dev mode, inconsistent with production mode * chore: revert 'removal' of component.compile_options.dev * feat: add test for destroying empty component * chore: update typechecking callback * chore: revert fragment dev checks * chore: remove unnecessary comment * chore: update test for empty-component-destroy * fix: revert back the patching of console.log * use before_test and after_test Co-authored-by: qinmu Co-authored-by: tanhauhau --- src/compiler/compile/render_dom/index.ts | 2 +- src/runtime/internal/transitions.ts | 4 ++- .../empty-component-destroy/Empty.svelte | 8 ++++++ .../empty-component-destroy/_config.js | 25 +++++++++++++++++++ .../empty-component-destroy/main.svelte | 8 ++++++ 5 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 test/runtime/samples/empty-component-destroy/Empty.svelte create mode 100644 test/runtime/samples/empty-component-destroy/_config.js create mode 100644 test/runtime/samples/empty-component-destroy/main.svelte diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts index 9d9699bdbf..5fdac9bce4 100644 --- a/src/compiler/compile/render_dom/index.ts +++ b/src/compiler/compile/render_dom/index.ts @@ -333,7 +333,7 @@ export default function dom( // $$props arg is still needed for unknown prop check args.push(x`$$props`); } - + // has_create_fragment is intentionally to be true in dev mode. const has_create_fragment = component.compile_options.dev || block.has_content(); if (has_create_fragment) { body.push(b` diff --git a/src/runtime/internal/transitions.ts b/src/runtime/internal/transitions.ts index a0134668ef..306a5e3793 100644 --- a/src/runtime/internal/transitions.ts +++ b/src/runtime/internal/transitions.ts @@ -79,6 +79,8 @@ export function transition_out(block: Fragment, local: 0 | 1, detach?: 0 | 1, ca }); block.o(local); + } else if (callback) { + callback(); } } @@ -143,7 +145,7 @@ export function create_in_transition(node: Element & ElementCSSInlineStyle, fn: return { start() { if (started) return; - + started = true; delete_rule(node); diff --git a/test/runtime/samples/empty-component-destroy/Empty.svelte b/test/runtime/samples/empty-component-destroy/Empty.svelte new file mode 100644 index 0000000000..14551c069a --- /dev/null +++ b/test/runtime/samples/empty-component-destroy/Empty.svelte @@ -0,0 +1,8 @@ + + diff --git a/test/runtime/samples/empty-component-destroy/_config.js b/test/runtime/samples/empty-component-destroy/_config.js new file mode 100644 index 0000000000..76b9ae4bb8 --- /dev/null +++ b/test/runtime/samples/empty-component-destroy/_config.js @@ -0,0 +1,25 @@ +let log; +export default { + html: ` + + `, + + before_test() { + log = console.log; + }, + after_test() { + console.log = log; + }, + + async test({ assert, target, window }) { + const button = target.querySelector('button'); + const event = new window.MouseEvent('click'); + const messages = []; + console.log = msg => messages.push(msg); + await button.dispatchEvent(event); + assert.htmlEqual(target.innerHTML, ` + + `); + assert.deepEqual(messages, ['destroy']); + } +}; diff --git a/test/runtime/samples/empty-component-destroy/main.svelte b/test/runtime/samples/empty-component-destroy/main.svelte new file mode 100644 index 0000000000..0ee5ef4a04 --- /dev/null +++ b/test/runtime/samples/empty-component-destroy/main.svelte @@ -0,0 +1,8 @@ + + + + + From 4583b170842208bcafcbb095221c8ac12689f739 Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Mon, 4 Jul 2022 09:48:12 +0800 Subject: [PATCH 003/168] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b5bc4858d8..4dbdb0bd5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * Fix `class:` directive updates with `` ([#7521](https://github.com/sveltejs/svelte/issues/7521), [#7571](https://github.com/sveltejs/svelte/issues/7571)) * Harden attribute escaping during ssr ([#7530](https://github.com/sveltejs/svelte/pull/7530)) * Add `ComponentType` and `ComponentProps` convenience types ([#6770](https://github.com/sveltejs/svelte/pull/6770)) +* Fix empty component not properly destroyed ([#7488](https://github.com/sveltejs/svelte/issues/7488)) ## 3.48.0 From 7e1691cd62df0593882480d00eb7e9a7616bb029 Mon Sep 17 00:00:00 2001 From: kindoflew Date: Mon, 4 Jul 2022 10:55:34 -0400 Subject: [PATCH 004/168] [fix] support @layer (#7514) * add test * add layer to apply check --- src/compiler/compile/css/Stylesheet.ts | 2 +- test/css/samples/at-layer/expected.css | 1 + test/css/samples/at-layer/input.svelte | 17 +++++++++++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 test/css/samples/at-layer/expected.css create mode 100644 test/css/samples/at-layer/input.svelte diff --git a/src/compiler/compile/css/Stylesheet.ts b/src/compiler/compile/css/Stylesheet.ts index 0a3098db6d..1a9ea7feeb 100644 --- a/src/compiler/compile/css/Stylesheet.ts +++ b/src/compiler/compile/css/Stylesheet.ts @@ -170,7 +170,7 @@ class Atrule { } apply(node: Element) { - if (this.node.name === 'media' || this.node.name === 'supports') { + if (this.node.name === 'media' || this.node.name === 'supports' || this.node.name === 'layer') { this.children.forEach(child => { child.apply(node); }); diff --git a/test/css/samples/at-layer/expected.css b/test/css/samples/at-layer/expected.css new file mode 100644 index 0000000000..e5f3de80c9 --- /dev/null +++ b/test/css/samples/at-layer/expected.css @@ -0,0 +1 @@ +@layer base, special;@layer special{div.svelte-xyz{color:rebeccapurple}}@layer base{div.svelte-xyz{color:green}} \ No newline at end of file diff --git a/test/css/samples/at-layer/input.svelte b/test/css/samples/at-layer/input.svelte new file mode 100644 index 0000000000..60289e1e5f --- /dev/null +++ b/test/css/samples/at-layer/input.svelte @@ -0,0 +1,17 @@ +
hello
+ + From a3ecb44b5346dbf116c5bec5dcf47cd7f459784d Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Mon, 4 Jul 2022 23:58:16 +0900 Subject: [PATCH 005/168] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4dbdb0bd5e..5e48244f4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * Harden attribute escaping during ssr ([#7530](https://github.com/sveltejs/svelte/pull/7530)) * Add `ComponentType` and `ComponentProps` convenience types ([#6770](https://github.com/sveltejs/svelte/pull/6770)) * Fix empty component not properly destroyed ([#7488](https://github.com/sveltejs/svelte/issues/7488)) +* Add CSS at-rule `@layer` support ([#7504](https://github.com/sveltejs/svelte/issues/7504)) ## 3.48.0 From 0fa0a38d5168a1767843fdb0a43c00aa30b8670f Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Wed, 6 Jul 2022 11:58:43 +0200 Subject: [PATCH 006/168] [fix] export CompileOptions (#7658) It's used by SvelteKit and in order to properly use it under the new TS moduleResolution NodeNext it needs to be part of a file that is defined in the exports map --- generate-type-definitions.js | 19 +++++++++++++++---- src/compiler/index.ts | 1 + src/runtime/index.ts | 2 +- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/generate-type-definitions.js b/generate-type-definitions.js index 6cad31807f..00b079eb5d 100644 --- a/generate-type-definitions.js +++ b/generate-type-definitions.js @@ -5,9 +5,20 @@ const { readFileSync, writeFileSync } = require('fs'); execSync('tsc -p src/compiler --emitDeclarationOnly && tsc -p src/runtime --emitDeclarationOnly'); -// We need to add these types to the index.d.ts here because if we add them before building, the build will fail, +// We need to add these types to the .d.ts files here because if we add them before building, the build will fail, // because the TS->JS transformation doesn't know these exports are types and produces code that fails at runtime. // We can't use `export type` syntax either because the TS version we're on doesn't have this feature yet. -const path = 'types/runtime/index.d.ts'; -const content = readFileSync(path, 'utf8'); -writeFileSync(path, content.replace('SvelteComponentTyped', 'SvelteComponentTyped, ComponentType, ComponentConstructorOptions, ComponentProps')); + +function modify(path, modifyFn) { + const content = readFileSync(path, 'utf8'); + writeFileSync(path, modifyFn(content)); +} + +modify( + 'types/runtime/index.d.ts', + content => content.replace('SvelteComponentTyped', 'SvelteComponentTyped, ComponentType, ComponentConstructorOptions, ComponentProps') +); +modify( + 'types/compiler/index.d.ts', + content => content + '\nexport { CompileOptions, ModuleFormat, EnableSourcemap, CssHashGetter } from "./interfaces"' +); diff --git a/src/compiler/index.ts b/src/compiler/index.ts index 14d55f5470..76eb45d37f 100644 --- a/src/compiler/index.ts +++ b/src/compiler/index.ts @@ -4,3 +4,4 @@ export { default as preprocess } from './preprocess/index'; export { walk } from 'estree-walker'; export const VERSION = '__VERSION__'; +// additional exports added through generate-type-definitions.js diff --git a/src/runtime/index.ts b/src/runtime/index.ts index 08b25ba2ca..2029f67a04 100644 --- a/src/runtime/index.ts +++ b/src/runtime/index.ts @@ -13,5 +13,5 @@ export { createEventDispatcher, SvelteComponentDev as SvelteComponent, SvelteComponentTyped - // additional exports added through post-typegen.js + // additional exports added through generate-type-definitions.js } from 'svelte/internal'; From 3798808e7484b7eeee6acb2860c45bb2e59d84bd Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Wed, 6 Jul 2022 11:59:44 +0200 Subject: [PATCH 007/168] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e48244f4a..0beea49892 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ * Add `ComponentType` and `ComponentProps` convenience types ([#6770](https://github.com/sveltejs/svelte/pull/6770)) * Fix empty component not properly destroyed ([#7488](https://github.com/sveltejs/svelte/issues/7488)) * Add CSS at-rule `@layer` support ([#7504](https://github.com/sveltejs/svelte/issues/7504)) +* Export `CompileOptions` from `svelte/compiler` ([#7658](https://github.com/sveltejs/svelte/pull/7658)) ## 3.48.0 From 52153dbce0237f0c36e4ff36377398d7f95276ef Mon Sep 17 00:00:00 2001 From: Conduitry Date: Thu, 7 Jul 2022 12:43:08 -0400 Subject: [PATCH 008/168] -> v3.49.0 --- CHANGELOG.md | 12 ++++++------ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0beea49892..70c05b01e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,14 +1,14 @@ # Svelte changelog -## Unreleased +## 3.49.0 -* Faster SSR ([#5701](https://github.com/sveltejs/svelte/pull/5701)) -* Fix `class:` directive updates with `` ([#7521](https://github.com/sveltejs/svelte/issues/7521), [#7571](https://github.com/sveltejs/svelte/issues/7571)) -* Harden attribute escaping during ssr ([#7530](https://github.com/sveltejs/svelte/pull/7530)) +* Improve performance of string escaping during SSR ([#5701](https://github.com/sveltejs/svelte/pull/5701)) * Add `ComponentType` and `ComponentProps` convenience types ([#6770](https://github.com/sveltejs/svelte/pull/6770)) -* Fix empty component not properly destroyed ([#7488](https://github.com/sveltejs/svelte/issues/7488)) -* Add CSS at-rule `@layer` support ([#7504](https://github.com/sveltejs/svelte/issues/7504)) +* Add support for CSS `@layer` ([#7504](https://github.com/sveltejs/svelte/issues/7504)) * Export `CompileOptions` from `svelte/compiler` ([#7658](https://github.com/sveltejs/svelte/pull/7658)) +* Fix DOM-less components not being properly destroyed ([#7488](https://github.com/sveltejs/svelte/issues/7488)) +* Fix `class:` directive updates with `` ([#7521](https://github.com/sveltejs/svelte/issues/7521), [#7571](https://github.com/sveltejs/svelte/issues/7571)) +* Harden attribute escaping during SSR ([#7530](https://github.com/sveltejs/svelte/pull/7530)) ## 3.48.0 diff --git a/package-lock.json b/package-lock.json index 83d963b827..38abdfc330 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "svelte", - "version": "3.48.0", + "version": "3.49.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "svelte", - "version": "3.48.0", + "version": "3.49.0", "license": "MIT", "devDependencies": { "@ampproject/remapping": "^0.3.0", diff --git a/package.json b/package.json index a1c9ae9a5d..de712b9bab 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "3.48.0", + "version": "3.49.0", "description": "Cybernetically enhanced web apps", "module": "index.mjs", "main": "index", From c01dc62314ccdff556ddd0c948dad2cb9128101d Mon Sep 17 00:00:00 2001 From: Sepehr Safari Date: Sun, 10 Jul 2022 11:52:00 +0430 Subject: [PATCH 009/168] [docs] fix typo (#7672) * [docs] fix typo Fixed "quckly" to "quickly" * [docs] fix typo Fixed "beend" to "been". * [docs] fix typo Fixed "propsoal" to "proposal". --- .../blog/2021-08-01-whats-new-in-svelte-august-2021.md | 2 +- .../blog/2021-10-01-whats-new-in-svelte-october-2021.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/site/content/blog/2021-08-01-whats-new-in-svelte-august-2021.md b/site/content/blog/2021-08-01-whats-new-in-svelte-august-2021.md index 0dd54ca22f..145d3aa784 100644 --- a/site/content/blog/2021-08-01-whats-new-in-svelte-august-2021.md +++ b/site/content/blog/2021-08-01-whats-new-in-svelte-august-2021.md @@ -59,7 +59,7 @@ To see all updates to SvelteKit, check out the [SvelteKit changelog](https://git - [Svelte-Capacitor](https://github.com/drannex42/svelte-capacitor/) just released v2.0.0 - making it even easier to build hybrid mobile apps for iOS and Android using Svelte and Capacitor with near native performance. - [svelte-remixicon](https://github.com/ABarnob/svelte-remixicon) is an icon library for Svelte based on Remix Icon, consisting of more than 2000 icons. - [SveltePress](https://github.com/GeopJr/SveltePress) is a documentation tool built on top of SvelteKit. -- [Svelte Starter Kit](https://github.com/one-aalam/svelte-starter-kit/tree/auth-supabase) is a boilerplate to quckly get up and running with Svelte, with Auth and User Profiles powered by Supabase. +- [Svelte Starter Kit](https://github.com/one-aalam/svelte-starter-kit/tree/auth-supabase) is a boilerplate to quickly get up and running with Svelte, with Auth and User Profiles powered by Supabase. - [Kahi UI](https://github.com/novacbn/kahi-ui) is a Svelte-first UI kit with Dark Mode built-in. - [typesafe-i18n](https://github.com/ivanhofer/typesafe-i18n) is an opinionated, fully type-safe, lightweight localization library for TypeScript and JavaScript projects with no external dependencies. diff --git a/site/content/blog/2021-10-01-whats-new-in-svelte-october-2021.md b/site/content/blog/2021-10-01-whats-new-in-svelte-october-2021.md index 5ffd65bb0a..4b5b65d6f0 100644 --- a/site/content/blog/2021-10-01-whats-new-in-svelte-october-2021.md +++ b/site/content/blog/2021-10-01-whats-new-in-svelte-october-2021.md @@ -33,7 +33,7 @@ Notable SvelteKit improvements this month include... - Svelte libraries should now work out-of-the-box without any Vite configuration ([#2343](https://github.com/sveltejs/kit/pull/2343)) - Improvements to package exports field ([#2345](https://github.com/sveltejs/kit/pull/2345) and [#2327](https://github.com/sveltejs/kit/pull/2327)) - [breaking] The `prerender.pages` config option has been renamed to `prerender.entries` ([#2380](https://github.com/sveltejs/kit/pull/2380)) -- A new generic argument has beend added to allow typing Body from hooks ([#2413](https://github.com/sveltejs/kit/pull/2413)) +- A new generic argument has been added to allow typing Body from hooks ([#2413](https://github.com/sveltejs/kit/pull/2413)) - The `svelte` field will be added to package.json when running the package command ([#2431](https://github.com/sveltejs/kit/pull/2431)) - [breaking] The `context` parameter of the load function was renamed to `stuff` ([#2439](https://github.com/sveltejs/kit/pull/2439)) - Added an `entryPoint` option for building a custom server with `adapter-node` ([#2414](https://github.com/sveltejs/kit/pull/2414)) @@ -92,7 +92,7 @@ Check out the community site [sveltesociety.dev](https://sveltesociety.dev/templ ## Before you go, answer the call for speakers! -Svelte Summit Fall 2021 (happening 20 November 2021) is looking for speakers. Submit your talk propsoal before 30 October... all are welcome to present and attend. +Svelte Summit Fall 2021 (happening 20 November 2021) is looking for speakers. Submit your talk proposal before 30 October... all are welcome to present and attend. ### More info on the [sessionize site](https://sessionize.com/svelte-summit-fall-2021/) From 1d19aebe38bed7bbaafdc304eb055f5bf455fb36 Mon Sep 17 00:00:00 2001 From: Mel Date: Sun, 10 Jul 2022 20:23:41 +0200 Subject: [PATCH 010/168] a11y: Add role-has-required-aria-props rule (#5852) * Check required props for ARIA roles * Test required ARIA props check * Properly indent with tabs in test * swtich to use aria-query * fix validation test * update docs Co-authored-by: tanhauhau --- package-lock.json | 29 + package.json | 2 + .../content/docs/05-accessibility-warnings.md | 11 + src/compiler/compile/compiler_warnings.ts | 4 + src/compiler/compile/nodes/Element.ts | 16 +- .../a11y-no-redundant-roles/warnings.json | 1367 +++++++++-------- .../input.svelte | 9 + .../warnings.json | 62 + 8 files changed, 867 insertions(+), 633 deletions(-) create mode 100644 test/validator/samples/a11y-role-has-required-aria-props/input.svelte create mode 100644 test/validator/samples/a11y-role-has-required-aria-props/warnings.json diff --git a/package-lock.json b/package-lock.json index 38abdfc330..ab4b20ed59 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,12 +18,14 @@ "@rollup/plugin-typescript": "^2.0.1", "@rollup/plugin-virtual": "^2.0.0", "@sveltejs/eslint-config": "github:sveltejs/eslint-config#v5.8.0", + "@types/aria-query": "^5.0.0", "@types/mocha": "^7.0.0", "@types/node": "^8.10.53", "@typescript-eslint/eslint-plugin": "^5.22.0", "@typescript-eslint/parser": "^5.22.0", "acorn": "^8.4.1", "agadoo": "^1.1.0", + "aria-query": "^5.0.0", "code-red": "^0.2.5", "css-tree": "^1.1.2", "eslint": "^8.0.0", @@ -285,6 +287,12 @@ "resolved": "git+ssh://git@github.com/sveltejs/eslint-config.git#31fd4faeea88990069502460b023698b1c9c2d13", "dev": true }, + "node_modules/@types/aria-query": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.0.tgz", + "integrity": "sha512-P+dkdFu0n08PDIvw+9nT9ByQnd+Udc8DaWPb9HKfaPwCvWvQpC5XaMRx2xLWECm9x1VKNps6vEAlirjA6+uNrQ==", + "dev": true + }, "node_modules/@types/estree": { "version": "0.0.50", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz", @@ -698,6 +706,15 @@ "sprintf-js": "~1.0.2" } }, + "node_modules/aria-query": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.0.0.tgz", + "integrity": "sha512-V+SM7AbUwJ+EBnB8+DXs0hPZHO0W6pqBcc0dW90OwtVG02PswOu/teuARoLQjdDOH+t9pJgGnW5/Qmouf3gPJg==", + "dev": true, + "engines": { + "node": ">=6.0" + } + }, "node_modules/array-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", @@ -4927,6 +4944,12 @@ "dev": true, "from": "@sveltejs/eslint-config@github:sveltejs/eslint-config#v5.8.0" }, + "@types/aria-query": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.0.tgz", + "integrity": "sha512-P+dkdFu0n08PDIvw+9nT9ByQnd+Udc8DaWPb9HKfaPwCvWvQpC5XaMRx2xLWECm9x1VKNps6vEAlirjA6+uNrQ==", + "dev": true + }, "@types/estree": { "version": "0.0.50", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz", @@ -5208,6 +5231,12 @@ "sprintf-js": "~1.0.2" } }, + "aria-query": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.0.0.tgz", + "integrity": "sha512-V+SM7AbUwJ+EBnB8+DXs0hPZHO0W6pqBcc0dW90OwtVG02PswOu/teuARoLQjdDOH+t9pJgGnW5/Qmouf3gPJg==", + "dev": true + }, "array-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", diff --git a/package.json b/package.json index de712b9bab..ade713f4e3 100644 --- a/package.json +++ b/package.json @@ -124,12 +124,14 @@ "@rollup/plugin-typescript": "^2.0.1", "@rollup/plugin-virtual": "^2.0.0", "@sveltejs/eslint-config": "github:sveltejs/eslint-config#v5.8.0", + "@types/aria-query": "^5.0.0", "@types/mocha": "^7.0.0", "@types/node": "^8.10.53", "@typescript-eslint/eslint-plugin": "^5.22.0", "@typescript-eslint/parser": "^5.22.0", "acorn": "^8.4.1", "agadoo": "^1.1.0", + "aria-query": "^5.0.0", "code-red": "^0.2.5", "css-tree": "^1.1.2", "eslint": "^8.0.0", diff --git a/site/content/docs/05-accessibility-warnings.md b/site/content/docs/05-accessibility-warnings.md index b39b06f9ff..9d0835b443 100644 --- a/site/content/docs/05-accessibility-warnings.md +++ b/site/content/docs/05-accessibility-warnings.md @@ -54,6 +54,17 @@ The following elements are visually distracting: `` and ``. --- +### `role-has-required-aria-props` + +Elements with ARIA roles must have all required attributes for that role. + +```sv + + +``` + +--- + ### `a11y-hidden` Certain DOM elements are useful for screen reader navigation and should not be hidden. diff --git a/src/compiler/compile/compiler_warnings.ts b/src/compiler/compile/compiler_warnings.ts index 86ec98cb8b..833722c3b9 100644 --- a/src/compiler/compile/compiler_warnings.ts +++ b/src/compiler/compile/compiler_warnings.ts @@ -80,6 +80,10 @@ export default { code: 'a11y-no-redundant-roles', message: `A11y: Redundant role '${role}'` }), + a11y_role_has_required_aria_props: (role: string, props: string[]) => ({ + code: 'a11y-role-has-required-aria-props', + message: `A11y: Elements with the ARIA role "${role}" must have the following attributes defined: ${props.map(name => `"${name}"`).join(', ')}` + }), a11y_accesskey: { code: 'a11y-accesskey', message: 'A11y: Avoid using accesskey' diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index 735562d627..35131cefcf 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -23,6 +23,7 @@ import { string_literal } from '../utils/stringify'; import { Literal } from 'estree'; import compiler_warnings from '../compiler_warnings'; import compiler_errors from '../compiler_errors'; +import { ARIARoleDefintionKey, roles } from 'aria-query'; const svg = /^(?:altGlyph|altGlyphDef|altGlyphItem|animate|animateColor|animateMotion|animateTransform|circle|clipPath|color-profile|cursor|defs|desc|discard|ellipse|feBlend|feColorMatrix|feComponentTransfer|feComposite|feConvolveMatrix|feDiffuseLighting|feDisplacementMap|feDistantLight|feDropShadow|feFlood|feFuncA|feFuncB|feFuncG|feFuncR|feGaussianBlur|feImage|feMerge|feMergeNode|feMorphology|feOffset|fePointLight|feSpecularLighting|feSpotLight|feTile|feTurbulence|filter|font|font-face|font-face-format|font-face-name|font-face-src|font-face-uri|foreignObject|g|glyph|glyphRef|hatch|hatchpath|hkern|image|line|linearGradient|marker|mask|mesh|meshgradient|meshpatch|meshrow|metadata|missing-glyph|mpath|path|pattern|polygon|polyline|radialGradient|rect|set|solidcolor|stop|svg|switch|symbol|text|textPath|tref|tspan|unknown|use|view|vkern)$/; @@ -407,9 +408,9 @@ export default class Element extends Node { } validate_attributes_a11y() { - const { component } = this; + const { component, attributes } = this; - this.attributes.forEach(attribute => { + attributes.forEach(attribute => { if (attribute.is_spread) return; const name = attribute.name.toLowerCase(); @@ -462,6 +463,17 @@ export default class Element extends Node { component.warn(attribute, compiler_warnings.a11y_no_redundant_roles(value)); } } + + // role-has-required-aria-props + const role = roles.get(value as ARIARoleDefintionKey); + if (role) { + const required_role_props = Object.keys(role.requiredProps); + const has_missing_props = required_role_props.some(prop => !attributes.find(a => a.name === prop)); + + if (has_missing_props) { + component.warn(attribute, compiler_warnings.a11y_role_has_required_aria_props(value as string, required_role_props)); + } + } } // no-access-key diff --git a/test/validator/samples/a11y-no-redundant-roles/warnings.json b/test/validator/samples/a11y-no-redundant-roles/warnings.json index 8aa3520b32..0947a467af 100644 --- a/test/validator/samples/a11y-no-redundant-roles/warnings.json +++ b/test/validator/samples/a11y-no-redundant-roles/warnings.json @@ -1,632 +1,737 @@ [ - { - "code": "a11y-no-redundant-roles", - "end": { - "character": 23, - "column": 23, - "line": 1 - }, - "message": "A11y: Redundant role 'link'", - "pos": 12, - "start": { - "character": 12, - "column": 12, - "line": 1 - } - }, - { - "code": "a11y-no-redundant-roles", - "end": { - "character": 58, - "column": 23, - "line": 2 - }, - "message": "A11y: Redundant role 'article'", - "pos": 44, - "start": { - "character": 44, - "column": 9, - "line": 2 - } - }, - { - "code": "a11y-no-redundant-roles", - "end": { - "character": 89, - "column": 27, - "line": 3 - }, - "message": "A11y: Redundant role 'complementary'", - "pos": 69, - "start": { - "character": 69, - "column": 7, - "line": 3 - } - }, - { - "code": "a11y-no-redundant-roles", - "end": { - "character": 114, - "column": 21, - "line": 4 - }, - "message": "A11y: Redundant role 'document'", - "pos": 99, - "start": { - "character": 99, - "column": 6, - "line": 4 - } - }, - { - "code": "a11y-no-redundant-roles", - "end": { - "character": 139, - "column": 21, - "line": 5 - }, - "message": "A11y: Redundant role 'button'", - "pos": 126, - "start": { - "character": 126, - "column": 8, - "line": 5 - } - }, - { - "code": "a11y-no-redundant-roles", - "end": { - "character": 167, - "column": 24, - "line": 6 - }, - "message": "A11y: Redundant role 'listbox'", - "pos": 153, - "start": { - "character": 153, - "column": 10, - "line": 6 - } - }, - { - "code": "a11y-no-redundant-roles", - "end": { - "character": 192, - "column": 21, - "line": 7 - }, - "message": "A11y: Redundant role 'definition'", - "pos": 175, - "start": { - "character": 175, - "column": 4, - "line": 7 - } - }, - { - "code": "a11y-no-redundant-roles", - "end": { - "character": 212, - "column": 16, - "line": 8 - }, - "message": "A11y: Redundant role 'term'", - "pos": 201, - "start": { - "character": 201, - "column": 5, - "line": 8 - } - }, - { - "code": "a11y-no-redundant-roles", - "end": { - "character": 237, - "column": 21, - "line": 9 - }, - "message": "A11y: Redundant role 'group'", - "pos": 225, - "start": { - "character": 225, - "column": 9, - "line": 9 - } - }, - { - "code": "a11y-no-redundant-roles", - "end": { - "character": 262, - "column": 21, - "line": 10 - }, - "message": "A11y: Redundant role 'dialog'", - "pos": 249, - "start": { - "character": 249, - "column": 8, - "line": 10 - } - }, - { - "code": "a11y-no-redundant-roles", - "end": { - "character": 281, - "column": 15, - "line": 11 - }, - "message": "A11y: Redundant role 'term'", - "pos": 270, - "start": { - "character": 270, - "column": 4, - "line": 11 - } - }, - { - "code": "a11y-no-redundant-roles", - "end": { - "character": 307, - "column": 22, - "line": 12 - }, - "message": "A11y: Redundant role 'group'", - "pos": 295, - "start": { - "character": 295, - "column": 10, - "line": 12 - } - }, - { - "code": "a11y-no-redundant-roles", - "end": { - "character": 332, - "column": 21, - "line": 13 - }, - "message": "A11y: Redundant role 'figure'", - "pos": 319, - "start": { - "character": 319, - "column": 8, - "line": 13 - } - }, - { - "code": "a11y-no-redundant-roles", - "end": { - "character": 353, - "column": 17, - "line": 14 - }, - "message": "A11y: Redundant role 'form'", - "pos": 342, - "start": { - "character": 342, - "column": 6, - "line": 14 - } - }, - { - "code": "a11y-no-redundant-roles", - "end": { - "character": 383, - "column": 18, - "line": 15 - }, - "message": "A11y: Redundant role 'heading'", - "pos": 369, - "start": { - "character": 369, - "column": 4, - "line": 15 - } - }, - { - "code": "a11y-no-redundant-roles", - "end": { - "character": 415, - "column": 18, - "line": 16 - }, - "message": "A11y: Redundant role 'heading'", - "pos": 401, - "start": { - "character": 401, - "column": 4, - "line": 16 - } - }, - { - "code": "a11y-no-redundant-roles", - "end": { - "character": 447, - "column": 18, - "line": 17 - }, - "message": "A11y: Redundant role 'heading'", - "pos": 433, - "start": { - "character": 433, - "column": 4, - "line": 17 - } - }, - { - "code": "a11y-no-redundant-roles", - "end": { - "character": 479, - "column": 18, - "line": 18 - }, - "message": "A11y: Redundant role 'heading'", - "pos": 465, - "start": { - "character": 465, - "column": 4, - "line": 18 - } - }, - { - "code": "a11y-no-redundant-roles", - "end": { - "character": 511, - "column": 18, - "line": 19 - }, - "message": "A11y: Redundant role 'heading'", - "pos": 497, - "start": { - "character": 497, - "column": 4, - "line": 19 - } - }, - { - "code": "a11y-no-redundant-roles", - "end": { - "character": 543, - "column": 18, - "line": 20 - }, - "message": "A11y: Redundant role 'heading'", - "pos": 529, - "start": { - "character": 529, - "column": 4, - "line": 20 - } - }, - { - "code": "a11y-no-redundant-roles", - "end": { - "character": 577, - "column": 20, - "line": 21 - }, - "message": "A11y: Redundant role 'separator'", - "pos": 561, - "start": { - "character": 561, - "column": 4, - "line": 21 - } - }, - { - "code": "a11y-no-redundant-roles", - "end": { - "character": 600, - "column": 19, - "line": 22 - }, - "message": "A11y: Redundant role 'listitem'", - "pos": 585, - "start": { - "character": 585, - "column": 4, - "line": 22 - } - }, - { - "code": "a11y-no-redundant-roles", - "end": { - "character": 621, - "column": 17, - "line": 23 - }, - "message": "A11y: Redundant role 'link'", - "pos": 610, - "start": { - "character": 610, - "column": 6, - "line": 23 - } - }, - { - "code": "a11y-no-redundant-roles", - "end": { - "character": 642, - "column": 17, - "line": 24 - }, - "message": "A11y: Redundant role 'main'", - "pos": 631, - "start": { - "character": 631, - "column": 6, - "line": 24 - } - }, - { - "code": "a11y-no-redundant-roles", - "end": { - "character": 668, - "column": 17, - "line": 25 - }, - "message": "A11y: Redundant role 'list'", - "pos": 657, - "start": { - "character": 657, - "column": 6, - "line": 25 - } - }, - { - "code": "a11y-no-redundant-roles", - "end": { - "character": 694, - "column": 22, - "line": 26 - }, - "message": "A11y: Redundant role 'navigation'", - "pos": 677, - "start": { - "character": 677, - "column": 5, - "line": 26 - } - }, - { - "code": "a11y-no-redundant-roles", - "end": { - "character": 713, - "column": 15, - "line": 27 - }, - "message": "A11y: Redundant role 'list'", - "pos": 702, - "start": { - "character": 702, - "column": 4, - "line": 27 - } - }, - { - "code": "a11y-no-redundant-roles", - "end": { - "character": 739, - "column": 22, - "line": 28 - }, - "message": "A11y: Redundant role 'group'", - "pos": 727, - "start": { - "character": 727, - "column": 10, - "line": 28 - } - }, - { - "code": "a11y-no-redundant-roles", - "end": { - "character": 764, - "column": 21, - "line": 29 - }, - "message": "A11y: Redundant role 'option'", - "pos": 751, - "start": { - "character": 751, - "column": 8, - "line": 29 - } - }, - { - "code": "a11y-no-redundant-roles", - "end": { - "character": 789, - "column": 21, - "line": 30 - }, - "message": "A11y: Redundant role 'status'", - "pos": 776, - "start": { - "character": 776, - "column": 8, - "line": 30 - } - }, - { - "code": "a11y-no-redundant-roles", - "end": { - "character": 821, - "column": 28, - "line": 31 - }, - "message": "A11y: Redundant role 'progressbar'", - "pos": 803, - "start": { - "character": 803, - "column": 10, - "line": 31 - } - }, - { - "code": "a11y-no-redundant-roles", - "end": { - "character": 847, - "column": 22, - "line": 32 - }, - "message": "A11y: Redundant role 'region'", - "pos": 834, - "start": { - "character": 834, - "column": 9, - "line": 32 - } - }, - { - "code": "a11y-no-redundant-roles", - "end": { - "character": 873, - "column": 22, - "line": 33 - }, - "message": "A11y: Redundant role 'button'", - "pos": 860, - "start": { - "character": 860, - "column": 9, - "line": 33 - } - }, - { - "code": "a11y-no-redundant-roles", - "end": { - "character": 896, - "column": 19, - "line": 34 - }, - "message": "A11y: Redundant role 'table'", - "pos": 884, - "start": { - "character": 884, - "column": 7, - "line": 34 - } - }, - { - "code": "a11y-no-redundant-roles", - "end": { - "character": 922, - "column": 22, - "line": 35 - }, - "message": "A11y: Redundant role 'rowgroup'", - "pos": 907, - "start": { - "character": 907, - "column": 7, - "line": 35 - } - }, - { - "code": "a11y-no-redundant-roles", - "end": { - "character": 950, - "column": 24, - "line": 36 - }, - "message": "A11y: Redundant role 'textbox'", - "pos": 936, - "start": { - "character": 936, - "column": 10, - "line": 36 - } - }, - { - "code": "a11y-no-redundant-roles", - "end": { - "character": 976, - "column": 22, - "line": 37 - }, - "message": "A11y: Redundant role 'rowgroup'", - "pos": 961, - "start": { - "character": 961, - "column": 7, - "line": 37 - } - }, - { - "code": "a11y-no-redundant-roles", - "end": { - "character": 1002, - "column": 22, - "line": 38 - }, - "message": "A11y: Redundant role 'rowgroup'", - "pos": 987, - "start": { - "character": 987, - "column": 7, - "line": 38 - } - }, - { - "code": "a11y-no-redundant-roles", - "end": { - "character": 1020, - "column": 14, - "line": 39 - }, - "message": "A11y: Redundant role 'row'", - "pos": 1010, - "start": { - "character": 1010, - "column": 4, - "line": 39 - } - }, - { - "code": "a11y-no-redundant-roles", - "end": { - "character": 1039, - "column": 15, - "line": 40 - }, - "message": "A11y: Redundant role 'list'", - "pos": 1028, - "start": { - "character": 1028, - "column": 4, - "line": 40 - } - }, - { - "code": "a11y-no-redundant-roles", - "end": { - "character": 1125, - "column": 21, - "line": 43 - }, - "message": "A11y: Redundant role 'banner'", - "pos": 1112, - "start": { - "character": 1112, - "column": 8, - "line": 43 - } - }, - { - "code": "a11y-no-redundant-roles", - "end": { - "character": 1162, - "column": 26, - "line": 44 - }, - "message": "A11y: Redundant role 'contentinfo'", - "pos": 1144, - "start": { - "character": 1144, - "column": 8, - "line": 44 - } - } -] \ No newline at end of file + { + "code": "a11y-no-redundant-roles", + "end": { + "character": 23, + "column": 23, + "line": 1 + }, + "message": "A11y: Redundant role 'link'", + "pos": 12, + "start": { + "character": 12, + "column": 12, + "line": 1 + } + }, + { + "code": "a11y-no-redundant-roles", + "end": { + "character": 58, + "column": 23, + "line": 2 + }, + "message": "A11y: Redundant role 'article'", + "pos": 44, + "start": { + "character": 44, + "column": 9, + "line": 2 + } + }, + { + "code": "a11y-no-redundant-roles", + "end": { + "character": 89, + "column": 27, + "line": 3 + }, + "message": "A11y: Redundant role 'complementary'", + "pos": 69, + "start": { + "character": 69, + "column": 7, + "line": 3 + } + }, + { + "code": "a11y-no-redundant-roles", + "end": { + "character": 114, + "column": 21, + "line": 4 + }, + "message": "A11y: Redundant role 'document'", + "pos": 99, + "start": { + "character": 99, + "column": 6, + "line": 4 + } + }, + { + "code": "a11y-no-redundant-roles", + "end": { + "character": 139, + "column": 21, + "line": 5 + }, + "message": "A11y: Redundant role 'button'", + "pos": 126, + "start": { + "character": 126, + "column": 8, + "line": 5 + } + }, + { + "code": "a11y-no-redundant-roles", + "end": { + "character": 167, + "column": 24, + "line": 6 + }, + "message": "A11y: Redundant role 'listbox'", + "pos": 153, + "start": { + "character": 153, + "column": 10, + "line": 6 + } + }, + { + "code": "a11y-no-redundant-roles", + "end": { + "character": 192, + "column": 21, + "line": 7 + }, + "message": "A11y: Redundant role 'definition'", + "pos": 175, + "start": { + "character": 175, + "column": 4, + "line": 7 + } + }, + { + "code": "a11y-no-redundant-roles", + "end": { + "character": 212, + "column": 16, + "line": 8 + }, + "message": "A11y: Redundant role 'term'", + "pos": 201, + "start": { + "character": 201, + "column": 5, + "line": 8 + } + }, + { + "code": "a11y-no-redundant-roles", + "end": { + "character": 237, + "column": 21, + "line": 9 + }, + "message": "A11y: Redundant role 'group'", + "pos": 225, + "start": { + "character": 225, + "column": 9, + "line": 9 + } + }, + { + "code": "a11y-no-redundant-roles", + "end": { + "character": 262, + "column": 21, + "line": 10 + }, + "message": "A11y: Redundant role 'dialog'", + "pos": 249, + "start": { + "character": 249, + "column": 8, + "line": 10 + } + }, + { + "code": "a11y-no-redundant-roles", + "end": { + "character": 281, + "column": 15, + "line": 11 + }, + "message": "A11y: Redundant role 'term'", + "pos": 270, + "start": { + "character": 270, + "column": 4, + "line": 11 + } + }, + { + "code": "a11y-no-redundant-roles", + "end": { + "character": 307, + "column": 22, + "line": 12 + }, + "message": "A11y: Redundant role 'group'", + "pos": 295, + "start": { + "character": 295, + "column": 10, + "line": 12 + } + }, + { + "code": "a11y-no-redundant-roles", + "end": { + "character": 332, + "column": 21, + "line": 13 + }, + "message": "A11y: Redundant role 'figure'", + "pos": 319, + "start": { + "character": 319, + "column": 8, + "line": 13 + } + }, + { + "code": "a11y-no-redundant-roles", + "end": { + "character": 353, + "column": 17, + "line": 14 + }, + "message": "A11y: Redundant role 'form'", + "pos": 342, + "start": { + "character": 342, + "column": 6, + "line": 14 + } + }, + { + "code": "a11y-no-redundant-roles", + "end": { + "character": 383, + "column": 18, + "line": 15 + }, + "message": "A11y: Redundant role 'heading'", + "pos": 369, + "start": { + "character": 369, + "column": 4, + "line": 15 + } + }, + { + "code": "a11y-role-has-required-aria-props", + "end": { + "character": 383, + "column": 18, + "line": 15 + }, + "message": "A11y: Elements with the ARIA role \"heading\" must have the following attributes defined: \"aria-level\"", + "pos": 369, + "start": { + "character": 369, + "column": 4, + "line": 15 + } + }, + { + "code": "a11y-no-redundant-roles", + "end": { + "character": 415, + "column": 18, + "line": 16 + }, + "message": "A11y: Redundant role 'heading'", + "pos": 401, + "start": { + "character": 401, + "column": 4, + "line": 16 + } + }, + { + "code": "a11y-role-has-required-aria-props", + "message": "A11y: Elements with the ARIA role \"heading\" must have the following attributes defined: \"aria-level\"", + "pos": 401, + "start": { + "character": 401, + "column": 4, + "line": 16 + }, + "end": { + "character": 415, + "column": 18, + "line": 16 + } + }, + { + "code": "a11y-no-redundant-roles", + "end": { + "character": 447, + "column": 18, + "line": 17 + }, + "message": "A11y: Redundant role 'heading'", + "pos": 433, + "start": { + "character": 433, + "column": 4, + "line": 17 + } + }, + { + "code": "a11y-role-has-required-aria-props", + "end": { + "character": 447, + "column": 18, + "line": 17 + }, + "message": "A11y: Elements with the ARIA role \"heading\" must have the following attributes defined: \"aria-level\"", + "pos": 433, + "start": { + "character": 433, + "column": 4, + "line": 17 + } + }, + { + "code": "a11y-no-redundant-roles", + "end": { + "character": 479, + "column": 18, + "line": 18 + }, + "message": "A11y: Redundant role 'heading'", + "pos": 465, + "start": { + "character": 465, + "column": 4, + "line": 18 + } + }, + { + "code": "a11y-role-has-required-aria-props", + "message": "A11y: Elements with the ARIA role \"heading\" must have the following attributes defined: \"aria-level\"", + "pos": 465, + "start": { + "character": 465, + "column": 4, + "line": 18 + }, + "end": { + "character": 479, + "column": 18, + "line": 18 + } + }, + { + "code": "a11y-no-redundant-roles", + "end": { + "character": 511, + "column": 18, + "line": 19 + }, + "message": "A11y: Redundant role 'heading'", + "pos": 497, + "start": { + "character": 497, + "column": 4, + "line": 19 + } + }, + { + "code": "a11y-role-has-required-aria-props", + "message": "A11y: Elements with the ARIA role \"heading\" must have the following attributes defined: \"aria-level\"", + "pos": 497, + "start": { + "character": 497, + "column": 4, + "line": 19 + }, + "end": { + "character": 511, + "column": 18, + "line": 19 + } + }, + { + "code": "a11y-no-redundant-roles", + "end": { + "character": 543, + "column": 18, + "line": 20 + }, + "message": "A11y: Redundant role 'heading'", + "pos": 529, + "start": { + "character": 529, + "column": 4, + "line": 20 + } + }, + { + "code": "a11y-role-has-required-aria-props", + "message": "A11y: Elements with the ARIA role \"heading\" must have the following attributes defined: \"aria-level\"", + "pos": 529, + "start": { + "character": 529, + "column": 4, + "line": 20 + }, + "end": { + "character": 543, + "column": 18, + "line": 20 + } + }, + { + "code": "a11y-no-redundant-roles", + "end": { + "character": 577, + "column": 20, + "line": 21 + }, + "message": "A11y: Redundant role 'separator'", + "pos": 561, + "start": { + "character": 561, + "column": 4, + "line": 21 + } + }, + { + "code": "a11y-no-redundant-roles", + "end": { + "character": 600, + "column": 19, + "line": 22 + }, + "message": "A11y: Redundant role 'listitem'", + "pos": 585, + "start": { + "character": 585, + "column": 4, + "line": 22 + } + }, + { + "code": "a11y-no-redundant-roles", + "end": { + "character": 621, + "column": 17, + "line": 23 + }, + "message": "A11y: Redundant role 'link'", + "pos": 610, + "start": { + "character": 610, + "column": 6, + "line": 23 + } + }, + { + "code": "a11y-no-redundant-roles", + "end": { + "character": 642, + "column": 17, + "line": 24 + }, + "message": "A11y: Redundant role 'main'", + "pos": 631, + "start": { + "character": 631, + "column": 6, + "line": 24 + } + }, + { + "code": "a11y-no-redundant-roles", + "end": { + "character": 668, + "column": 17, + "line": 25 + }, + "message": "A11y: Redundant role 'list'", + "pos": 657, + "start": { + "character": 657, + "column": 6, + "line": 25 + } + }, + { + "code": "a11y-no-redundant-roles", + "end": { + "character": 694, + "column": 22, + "line": 26 + }, + "message": "A11y: Redundant role 'navigation'", + "pos": 677, + "start": { + "character": 677, + "column": 5, + "line": 26 + } + }, + { + "code": "a11y-no-redundant-roles", + "end": { + "character": 713, + "column": 15, + "line": 27 + }, + "message": "A11y: Redundant role 'list'", + "pos": 702, + "start": { + "character": 702, + "column": 4, + "line": 27 + } + }, + { + "code": "a11y-no-redundant-roles", + "end": { + "character": 739, + "column": 22, + "line": 28 + }, + "message": "A11y: Redundant role 'group'", + "pos": 727, + "start": { + "character": 727, + "column": 10, + "line": 28 + } + }, + { + "code": "a11y-no-redundant-roles", + "end": { + "character": 764, + "column": 21, + "line": 29 + }, + "message": "A11y: Redundant role 'option'", + "pos": 751, + "start": { + "character": 751, + "column": 8, + "line": 29 + } + }, + { + "code": "a11y-role-has-required-aria-props", + "message": "A11y: Elements with the ARIA role \"option\" must have the following attributes defined: \"aria-selected\"", + "pos": 751, + "start": { + "character": 751, + "column": 8, + "line": 29 + }, + "end": { + "character": 764, + "column": 21, + "line": 29 + } + }, + { + "code": "a11y-no-redundant-roles", + "end": { + "character": 789, + "column": 21, + "line": 30 + }, + "message": "A11y: Redundant role 'status'", + "pos": 776, + "start": { + "character": 776, + "column": 8, + "line": 30 + } + }, + { + "code": "a11y-no-redundant-roles", + "end": { + "character": 821, + "column": 28, + "line": 31 + }, + "message": "A11y: Redundant role 'progressbar'", + "pos": 803, + "start": { + "character": 803, + "column": 10, + "line": 31 + } + }, + { + "code": "a11y-no-redundant-roles", + "end": { + "character": 847, + "column": 22, + "line": 32 + }, + "message": "A11y: Redundant role 'region'", + "pos": 834, + "start": { + "character": 834, + "column": 9, + "line": 32 + } + }, + { + "code": "a11y-no-redundant-roles", + "end": { + "character": 873, + "column": 22, + "line": 33 + }, + "message": "A11y: Redundant role 'button'", + "pos": 860, + "start": { + "character": 860, + "column": 9, + "line": 33 + } + }, + { + "code": "a11y-no-redundant-roles", + "end": { + "character": 896, + "column": 19, + "line": 34 + }, + "message": "A11y: Redundant role 'table'", + "pos": 884, + "start": { + "character": 884, + "column": 7, + "line": 34 + } + }, + { + "code": "a11y-no-redundant-roles", + "end": { + "character": 922, + "column": 22, + "line": 35 + }, + "message": "A11y: Redundant role 'rowgroup'", + "pos": 907, + "start": { + "character": 907, + "column": 7, + "line": 35 + } + }, + { + "code": "a11y-no-redundant-roles", + "end": { + "character": 950, + "column": 24, + "line": 36 + }, + "message": "A11y: Redundant role 'textbox'", + "pos": 936, + "start": { + "character": 936, + "column": 10, + "line": 36 + } + }, + { + "code": "a11y-no-redundant-roles", + "end": { + "character": 976, + "column": 22, + "line": 37 + }, + "message": "A11y: Redundant role 'rowgroup'", + "pos": 961, + "start": { + "character": 961, + "column": 7, + "line": 37 + } + }, + { + "code": "a11y-no-redundant-roles", + "end": { + "character": 1002, + "column": 22, + "line": 38 + }, + "message": "A11y: Redundant role 'rowgroup'", + "pos": 987, + "start": { + "character": 987, + "column": 7, + "line": 38 + } + }, + { + "code": "a11y-no-redundant-roles", + "end": { + "character": 1020, + "column": 14, + "line": 39 + }, + "message": "A11y: Redundant role 'row'", + "pos": 1010, + "start": { + "character": 1010, + "column": 4, + "line": 39 + } + }, + { + "code": "a11y-no-redundant-roles", + "end": { + "character": 1039, + "column": 15, + "line": 40 + }, + "message": "A11y: Redundant role 'list'", + "pos": 1028, + "start": { + "character": 1028, + "column": 4, + "line": 40 + } + }, + { + "code": "a11y-no-redundant-roles", + "end": { + "character": 1125, + "column": 21, + "line": 43 + }, + "message": "A11y: Redundant role 'banner'", + "pos": 1112, + "start": { + "character": 1112, + "column": 8, + "line": 43 + } + }, + { + "code": "a11y-no-redundant-roles", + "end": { + "character": 1162, + "column": 26, + "line": 44 + }, + "message": "A11y: Redundant role 'contentinfo'", + "pos": 1144, + "start": { + "character": 1144, + "column": 8, + "line": 44 + } + } +] diff --git a/test/validator/samples/a11y-role-has-required-aria-props/input.svelte b/test/validator/samples/a11y-role-has-required-aria-props/input.svelte new file mode 100644 index 0000000000..8e88b08908 --- /dev/null +++ b/test/validator/samples/a11y-role-has-required-aria-props/input.svelte @@ -0,0 +1,9 @@ +
+ +
+
+ +
+ +
+
diff --git a/test/validator/samples/a11y-role-has-required-aria-props/warnings.json b/test/validator/samples/a11y-role-has-required-aria-props/warnings.json new file mode 100644 index 0000000000..4de7c84001 --- /dev/null +++ b/test/validator/samples/a11y-role-has-required-aria-props/warnings.json @@ -0,0 +1,62 @@ +[ + { + "code": "a11y-role-has-required-aria-props", + "message": "A11y: Elements with the ARIA role \"heading\" must have the following attributes defined: \"aria-level\"", + "start": { + "line": 1, + "column": 5, + "character": 5 + }, + "end": { + "line": 1, + "column": 19, + "character": 19 + }, + "pos": 5 + }, + { + "code": "a11y-role-has-required-aria-props", + "message": "A11y: Elements with the ARIA role \"checkbox\" must have the following attributes defined: \"aria-checked\"", + "start": { + "line": 2, + "column": 6, + "character": 33 + }, + "end": { + "line": 2, + "column": 21, + "character": 48 + }, + "pos": 33 + }, + { + "code": "a11y-role-has-required-aria-props", + "message": "A11y: Elements with the ARIA role \"meter\" must have the following attributes defined: \"aria-valuenow\"", + "start": { + "line": 3, + "column": 5, + "character": 62 + }, + "end": { + "line": 3, + "column": 17, + "character": 74 + }, + "pos": 62 + }, + { + "code": "a11y-role-has-required-aria-props", + "message": "A11y: Elements with the ARIA role \"scrollbar\" must have the following attributes defined: \"aria-controls\", \"aria-valuenow\"", + "start": { + "character": 87, + "column": 5, + "line": 4 + }, + "end": { + "character": 103, + "column": 21, + "line": 4 + }, + "pos": 87 + } +] From 4617c0d5f5af1dae09cc00ce0134e433588a62d1 Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Mon, 11 Jul 2022 02:27:58 +0800 Subject: [PATCH 011/168] Update CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 70c05b01e9..4140333c7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Svelte changelog +## Unreleased + +* Add a11y warning `a11y-role-has-required-aria-props` which checks that elements with `role` attribute has all required attributes for that role. ([#5852](https://github.com/sveltejs/svelte/pull/5852)) + ## 3.49.0 * Improve performance of string escaping during SSR ([#5701](https://github.com/sveltejs/svelte/pull/5701)) From 39901986d6bc3b1a47d607f2f5e93cbb9c9445bb Mon Sep 17 00:00:00 2001 From: kwangure <86817609+kwangure@users.noreply.github.com> Date: Mon, 11 Jul 2022 04:40:12 +0300 Subject: [PATCH 012/168] [feat]: Add A11y aria-proptypes check (#6978) * Add aria prop type list * feat: check aria attribute types * feat: add proptype tests * Add documentation * use aria-query Co-authored-by: Nurassyl Zekenov Co-authored-by: mka_882@yahoo.com Co-authored-by: tanhauhau Co-authored-by: David Mosher --- .../content/docs/05-accessibility-warnings.md | 12 +++ src/compiler/compile/compiler_warnings.ts | 33 +++++++- src/compiler/compile/nodes/Element.ts | 40 +++++++++- .../a11y-aria-proptypes-boolean/input.svelte | 8 ++ .../a11y-aria-proptypes-boolean/warnings.json | 32 ++++++++ .../a11y-aria-proptypes-integer/input.svelte | 7 ++ .../a11y-aria-proptypes-integer/warnings.json | 47 +++++++++++ .../a11y-aria-proptypes-number/input.svelte | 7 ++ .../a11y-aria-proptypes-number/warnings.json | 47 +++++++++++ .../a11y-aria-proptypes-string/input.svelte | 5 ++ .../a11y-aria-proptypes-string/warnings.json | 17 ++++ .../a11y-aria-proptypes-token/input.svelte | 6 ++ .../a11y-aria-proptypes-token/warnings.json | 62 +++++++++++++++ .../input.svelte | 7 ++ .../warnings.json | 77 +++++++++++++++++++ .../a11y-aria-proptypes-tristate/input.svelte | 8 ++ .../warnings.json | 32 ++++++++ 17 files changed, 445 insertions(+), 2 deletions(-) create mode 100644 test/validator/samples/a11y-aria-proptypes-boolean/input.svelte create mode 100644 test/validator/samples/a11y-aria-proptypes-boolean/warnings.json create mode 100644 test/validator/samples/a11y-aria-proptypes-integer/input.svelte create mode 100644 test/validator/samples/a11y-aria-proptypes-integer/warnings.json create mode 100644 test/validator/samples/a11y-aria-proptypes-number/input.svelte create mode 100644 test/validator/samples/a11y-aria-proptypes-number/warnings.json create mode 100644 test/validator/samples/a11y-aria-proptypes-string/input.svelte create mode 100644 test/validator/samples/a11y-aria-proptypes-string/warnings.json create mode 100644 test/validator/samples/a11y-aria-proptypes-token/input.svelte create mode 100644 test/validator/samples/a11y-aria-proptypes-token/warnings.json create mode 100644 test/validator/samples/a11y-aria-proptypes-tokenlist/input.svelte create mode 100644 test/validator/samples/a11y-aria-proptypes-tokenlist/warnings.json create mode 100644 test/validator/samples/a11y-aria-proptypes-tristate/input.svelte create mode 100644 test/validator/samples/a11y-aria-proptypes-tristate/warnings.json diff --git a/site/content/docs/05-accessibility-warnings.md b/site/content/docs/05-accessibility-warnings.md index 9d0835b443..438f2e4743 100644 --- a/site/content/docs/05-accessibility-warnings.md +++ b/site/content/docs/05-accessibility-warnings.md @@ -98,6 +98,18 @@ Enforce img alt attribute does not contain the word image, picture, or photo. Sc --- +### `a11y-incorrect-aria-attribute-type` + +Enforce that only the correct type of value is used for aria attributes. For example, `aria-hidden` +should only receive a boolean. + +```sv + +
+``` + +--- + ### `a11y-invalid-attribute` Enforce that attributes important for accessibility have a valid value. For example, `href` should not be empty, `'#'`, or `javascript:`. diff --git a/src/compiler/compile/compiler_warnings.ts b/src/compiler/compile/compiler_warnings.ts index 833722c3b9..267a97afcc 100644 --- a/src/compiler/compile/compiler_warnings.ts +++ b/src/compiler/compile/compiler_warnings.ts @@ -1,12 +1,14 @@ // All compiler warnings should be listed and accessed from here +import { ARIAPropertyDefinition } from 'aria-query'; + /** * @internal */ export default { custom_element_no_tag: { code: 'custom-element-no-tag', - message: 'No custom element \'tag\' option was specified. To automatically register a custom element, specify a name with a hyphen in it, e.g. . To hide this warning, use ' + message: 'No custom element \'tag\' option was specified. To automatically register a custom element, specify a name with a hyphen in it, e.g. . To hide this warning, use ' }, unused_export_let: (component: string, property: string) => ({ code: 'unused-export-let', @@ -60,6 +62,35 @@ export default { code: 'a11y-aria-attributes', message: `A11y: <${name}> should not have aria-* attributes` }), + a11y_incorrect_attribute_type: (schema: ARIAPropertyDefinition, attribute: string) => { + 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 { + code: 'a11y-incorrect-aria-attribute-type', + message: `A11y: ${message}` + }; + }, a11y_unknown_aria_attribute: (attribute: string, suggestion?: string) => ({ code: 'a11y-unknown-aria-attribute', message: `A11y: Unknown aria attribute 'aria-${attribute}'` + (suggestion ? ` (did you mean '${suggestion}'?)` : '') diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index 35131cefcf..b7b2572986 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -23,7 +23,7 @@ import { string_literal } from '../utils/stringify'; import { Literal } from 'estree'; import compiler_warnings from '../compiler_warnings'; import compiler_errors from '../compiler_errors'; -import { ARIARoleDefintionKey, roles } from 'aria-query'; +import { ARIARoleDefintionKey, roles, aria, ARIAPropertyDefinition, ARIAProperty } from 'aria-query'; const svg = /^(?:altGlyph|altGlyphDef|altGlyphItem|animate|animateColor|animateMotion|animateTransform|circle|clipPath|color-profile|cursor|defs|desc|discard|ellipse|feBlend|feColorMatrix|feComponentTransfer|feComposite|feConvolveMatrix|feDiffuseLighting|feDisplacementMap|feDistantLight|feDropShadow|feFlood|feFuncA|feFuncB|feFuncG|feFuncR|feGaussianBlur|feImage|feMerge|feMergeNode|feMorphology|feOffset|fePointLight|feSpecularLighting|feSpotLight|feTile|feTurbulence|filter|font|font-face|font-face-format|font-face-name|font-face-src|font-face-uri|foreignObject|g|glyph|glyphRef|hatch|hatchpath|hkern|image|line|linearGradient|marker|mask|mesh|meshgradient|meshpatch|meshrow|metadata|missing-glyph|mpath|path|pattern|polygon|polyline|radialGradient|rect|set|solidcolor|stop|svg|switch|symbol|text|textPath|tref|tspan|unknown|use|view|vkern)$/; @@ -177,6 +177,32 @@ function get_namespace(parent: Element, element: Element, explicit_namespace: st return parent_element.namespace; } +function is_valid_aria_attribute_value(schema: ARIAPropertyDefinition, value: string | boolean): boolean { + switch (schema.type) { + case 'boolean': + return typeof value === 'boolean'; + case 'string': + case 'id': + return typeof value === 'string'; + case 'tristate': + return typeof value === 'boolean' || value === 'mixed'; + case 'integer': + case 'number': + return typeof value !== 'boolean' && isNaN(Number(value)) === false; + case 'token': // single token + return (schema.values || []) + .indexOf(typeof value === 'string' ? value.toLowerCase() : value) > -1; + case 'idlist': // if list of ids, split each + return typeof value === 'string' + && value.split(' ').every((id) => typeof id === 'string'); + case 'tokenlist': // if list of tokens, split each + return typeof value === 'string' + && value.split(' ').every((token) => (schema.values || []).indexOf(token.toLowerCase()) > -1); + default: + return false; + } +} + export default class Element extends Node { type: 'Element'; name: string; @@ -431,6 +457,18 @@ export default class Element extends Node { if (name === 'aria-hidden' && /^h[1-6]$/.test(this.name)) { component.warn(attribute, compiler_warnings.a11y_hidden(this.name)); } + + // aria-proptypes + let value = attribute.get_static_value(); + if (value === 'true') value = true; + if (value === 'false') value = false; + + if (value !== null && value !== undefined && aria.has(name as ARIAProperty)) { + const schema = aria.get(name as ARIAProperty); + if (!is_valid_aria_attribute_value(schema, value)) { + component.warn(attribute, compiler_warnings.a11y_incorrect_attribute_type(schema, name)); + } + } } // aria-role diff --git a/test/validator/samples/a11y-aria-proptypes-boolean/input.svelte b/test/validator/samples/a11y-aria-proptypes-boolean/input.svelte new file mode 100644 index 0000000000..c992928229 --- /dev/null +++ b/test/validator/samples/a11y-aria-proptypes-boolean/input.svelte @@ -0,0 +1,8 @@ + + +