From 9f2d931dae38fccc583a101fc345833850d8fa64 Mon Sep 17 00:00:00 2001 From: mhkeller Date: Fri, 17 Jul 2026 19:18:42 -0400 Subject: [PATCH 01/12] ts fixes to site --- jsconfig.site.json | 13 +++ package.json | 1 + scripts/check-site.js | 100 ++++++++++++++++++ src/_modules/arrowUtils.js | 34 +++++- src/_modules/calcThresholds.js | 7 +- src/_modules/cleanTitle.js | 4 + src/_modules/constructReplLink.js | 14 ++- src/_modules/downloadBlob.js | 9 +- src/_modules/getSections.js | 37 +++++-- src/_modules/hljsDefineSvelte.js | 1 + src/_modules/processMarkdown.js | 5 + src/_modules/slugify.js | 6 ++ .../_site-components/DownloadBtn.svelte | 52 +++++---- .../DownloadComponentBtn.svelte | 2 +- .../_site-components/GuideContents.svelte | 2 +- src/routes/_site-components/Nav.svelte | 2 +- src/routes/api/guide-sections.json/+server.js | 1 + src/routes/components/+page.svelte | 56 ++++++---- src/routes/components/[slug].json/+server.js | 36 +++++-- src/routes/components/[slug]/+page.svelte | 16 ++- src/routes/example-ssr/[slug].json/+server.js | 28 +++-- src/routes/example-ssr/[slug]/+page.svelte | 2 + src/routes/example/[slug].json/+server.js | 27 +++-- src/routes/example/[slug]/+page.svelte | 2 + src/routes/guide.json/+server.js | 1 + src/routes/guide/+page.svelte | 18 ++-- 26 files changed, 373 insertions(+), 103 deletions(-) create mode 100644 jsconfig.site.json create mode 100644 scripts/check-site.js diff --git a/jsconfig.site.json b/jsconfig.site.json new file mode 100644 index 000000000..565d81b33 --- /dev/null +++ b/jsconfig.site.json @@ -0,0 +1,13 @@ +{ + "extends": "./jsconfig.json", + "exclude": [ + "./src/lib/**", + "./src/_components/**", + "./src/routes/_components/**", + "./src/routes/_components_ssr/**", + "./src/routes/_examples/**", + "./src/routes/_examples_ssr/**", + "./src/_data/*", + "./src/scripts/**/*" + ] +} diff --git a/package.json b/package.json index 7edd672f7..1a121abeb 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "package": "svelte-kit sync && svelte-package -o dist && publint", "preview": "vite preview", "check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json", + "check:site": "svelte-kit sync && node ./scripts/check-site.js", "lint": "prettier --check .", "format": "prettier --write .", "update_template": "sh ./src/scripts/update_template.sh", diff --git a/scripts/check-site.js b/scripts/check-site.js new file mode 100644 index 000000000..6b9e97026 --- /dev/null +++ b/scripts/check-site.js @@ -0,0 +1,100 @@ +#!/usr/bin/env node +/** + * Run svelte-check for internal site code only, ignoring: + * - src/lib (published library) + * - chart demos under src/_components and src/routes/_components* + * - example charts under src/routes/_examples* + */ +import { spawnSync } from 'node:child_process'; +import { createRequire } from 'node:module'; + +const require = createRequire(import.meta.url); +const svelteCheckBin = require.resolve('svelte-check/bin/svelte-check'); + +/** Paths deferred to a later pass — not part of this site-only check. */ +const ignoredPathParts = [ + '/src/lib/', + '\\src\\lib\\', + '/src/_components/', + '\\src\\_components\\', + '/src/routes/_components/', + '\\src\\routes\\_components\\', + '/src/routes/_components_ssr/', + '\\src\\routes\\_components_ssr\\', + '/src/routes/_examples/', + '\\src\\routes\\_examples\\', + '/src/routes/_examples_ssr/', + '\\src\\routes\\_examples_ssr\\' +]; + +/** + * @param {string} file + */ +function isIgnored(file) { + return ignoredPathParts.some(part => file.includes(part)); +} + +const result = spawnSync( + process.execPath, + [svelteCheckBin, '--tsconfig', './jsconfig.site.json'], + { encoding: 'utf8', maxBuffer: 20 * 1024 * 1024 } +); + +const output = result.stdout || ''; +process.stdout.write(output); +if (result.stderr) process.stderr.write(result.stderr); + +const lines = output.split('\n'); +/** @type {{ file: string, line: number, col: number, msg: string }[]} */ +const siteErrors = []; +/** @type {string | null} */ +let currentFile = null; +let currentLine = 0; +let currentCol = 0; +/** @type {string | null} */ +let currentKind = null; +/** @type {string[]} */ +let currentMsg = []; + +function flush() { + if (!currentFile || currentKind !== 'Error') return; + if (isIgnored(currentFile)) return; + siteErrors.push({ + file: currentFile, + line: currentLine, + col: currentCol, + msg: currentMsg.join('\n').trim() + }); +} + +for (const line of lines) { + const locParts = line.match(/^(\/[^\s:]+):(\d+):(\d+)$/); + if (locParts) { + flush(); + currentFile = locParts[1]; + currentLine = Number(locParts[2]); + currentCol = Number(locParts[3]); + currentKind = null; + currentMsg = []; + continue; + } + const kind = line.match(/^(Error|Warn|Hint):\s*(.*)$/); + if (kind && currentFile) { + flush(); + currentKind = kind[1]; + currentMsg = [kind[2]]; + continue; + } + if (currentKind && currentFile && line && !line.startsWith('=====')) { + currentMsg.push(line); + } +} +flush(); + +if (siteErrors.length) { + console.error(`\ncheck:site found ${siteErrors.length} internal-site error(s) (lib/charts ignored).`); + process.exit(1); +} + +console.log('\ncheck:site passed (no internal-site errors; lib/charts ignored).'); +process.exit(0); diff --git a/src/_modules/arrowUtils.js b/src/_modules/arrowUtils.js index d29e7a0da..fe343d5a0 100644 --- a/src/_modules/arrowUtils.js +++ b/src/_modules/arrowUtils.js @@ -9,6 +9,13 @@ * A pixel value, which will parse as a number * */ +/** + * @param {string|number|null|undefined} d + * @param {number} i + * @param {number} width + * @param {number} height + * @returns {number} + */ export function parseCssValue(d, i, width, height) { if (!d) return 0; if (typeof d === 'number') { @@ -27,9 +34,13 @@ export function parseCssValue(d, i, width, height) { * that we can attach arrow starting points to * */ +/** + * @param {Element} el + * @returns {{ top: number, right: number, bottom: number, left: number, width: number, height: number }} + */ export function getElPosition(el) { const annotationBbox = el.getBoundingClientRect(); - const parentBbox = el.parentNode.getBoundingClientRect(); + const parentBbox = /** @type {Element} */ (el.parentNode).getBoundingClientRect(); const coords = { top: annotationBbox.top - parentBbox.top, right: annotationBbox.right - parentBbox.left, @@ -50,13 +61,24 @@ export function getElPosition(el) { export function swoopyArrow() { let angle = Math.PI; let clockwise = true; + /** @type {(d: any) => number} */ let xValue = d => d[0]; + /** @type {(d: any) => number} */ let yValue = d => d[1]; + /** + * @param {number} a + * @param {number} b + * @returns {number} + */ function hypotenuse(a, b) { return Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2)); } + /** + * @param {any[]} data + * @returns {string} + */ function render(data) { data = data.map(d => { return [xValue(d), yValue(d)]; @@ -102,27 +124,31 @@ export function swoopyArrow() { return path; } + /** @param {number} [_] */ render.angle = function renderAngle(_) { if (!arguments.length) return angle; - angle = Math.min(Math.max(_, 1e-6), Math.PI - 1e-6); + angle = Math.min(Math.max(/** @type {number} */ (_), 1e-6), Math.PI - 1e-6); return render; }; + /** @param {boolean} [_] */ render.clockwise = function renderClockwise(_) { if (!arguments.length) return clockwise; clockwise = !!_; return render; }; + /** @param {(d: any) => number} [_] */ render.x = function renderX(_) { if (!arguments.length) return xValue; - xValue = _; + xValue = /** @type {(d: any) => number} */ (_); return render; }; + /** @param {(d: any) => number} [_] */ render.y = function renderY(_) { if (!arguments.length) return yValue; - yValue = _; + yValue = /** @type {(d: any) => number} */ (_); return render; }; diff --git a/src/_modules/calcThresholds.js b/src/_modules/calcThresholds.js index 7962b319f..8ddf3f19c 100644 --- a/src/_modules/calcThresholds.js +++ b/src/_modules/calcThresholds.js @@ -1,4 +1,9 @@ -export default function calcThresholds(domain = [0, 1], n) { +/** + * @param {[number, number]} [domain] + * @param {number} [n] + * @returns {number[]} + */ +export default function calcThresholds(domain = [0, 1], n = 1) { const breaks = [domain[0]]; const brk = (domain[1] - domain[0]) / n; while (breaks[breaks.length - 1] < domain[1]) { diff --git a/src/_modules/cleanTitle.js b/src/_modules/cleanTitle.js index 092e03988..b06c4b1af 100644 --- a/src/_modules/cleanTitle.js +++ b/src/_modules/cleanTitle.js @@ -1,3 +1,7 @@ +/** + * @param {string} title + * @returns {string} + */ export default function cleanTitle(title) { const parts = title.split('/'); const nameParts = parts[parts.length - 1].split('.'); diff --git a/src/_modules/constructReplLink.js b/src/_modules/constructReplLink.js index 3df2fed53..0830f7dac 100644 --- a/src/_modules/constructReplLink.js +++ b/src/_modules/constructReplLink.js @@ -2,6 +2,18 @@ import { csvParse } from 'd3-dsv'; import { compress_and_encode_text } from './createReplHash.js'; +/** + * @param {string} pageName + * @param {{ + * main: { title: string, contents: string }, + * components: { title: string, contents: string }[], + * componentModules: { title: string, contents: string }[], + * modules: { title: string, contents: string }[], + * componentComponents: { title: string, contents: string }[], + * jsons: { title: string, contents: string }[], + * csvs: { title: string, contents: string }[] + * }} content + */ export default async function constructReplLink(pageName, content) { // TODO, clean up import paths const pages = [content.main] @@ -17,7 +29,7 @@ export default async function constructReplLink(pageName, content) { const json = { name: pageTitle.trim(), files: pages.map(c => { - const filename = c.title.split('/').pop(); + const filename = c.title.split('/').pop() ?? ''; const name = cleanName(filename); return { type: 'file', diff --git a/src/_modules/downloadBlob.js b/src/_modules/downloadBlob.js index 1f2129eaf..3d7342525 100644 --- a/src/_modules/downloadBlob.js +++ b/src/_modules/downloadBlob.js @@ -1,9 +1,14 @@ +/** + * @param {BlobPart|Blob} blob + * @param {string} filename + * @param {boolean} [createBlob=false] + */ export default function downloadBlob(blob, filename, createBlob = false) { let myBlob; if (createBlob === true) { - myBlob = new Blob([blob], { type: 'octet/stream' }); + myBlob = new Blob([/** @type {BlobPart} */ (blob)], { type: 'octet/stream' }); } else { - myBlob = blob; + myBlob = /** @type {Blob} */ (blob); } const url = URL.createObjectURL(myBlob); const link = document.createElement('a'); diff --git a/src/_modules/getSections.js b/src/_modules/getSections.js index bc5d9b4f7..838ab33d8 100644 --- a/src/_modules/getSections.js +++ b/src/_modules/getSections.js @@ -13,6 +13,7 @@ hljs.registerLanguage('svelte', hljsDefineSvelte); hljsDefineSvelte(hljs); +/** @type {Record} */ const escaped = { '"': '"', "'": ''', @@ -21,11 +22,13 @@ const escaped = { '>': '>' }; +/** @type {Record} */ const unescaped = Object.keys(escaped).reduce( - (unescaped, key) => ((unescaped[escaped[key]] = key), unescaped), - {} + (acc, key) => ((acc[escaped[key]] = key), acc), + /** @type {Record} */ ({}) ); +/** @param {string} str */ function unescape(str) { return String(str).replace(/&.+?;/g, match => unescaped[match] || match); } @@ -33,6 +36,10 @@ function unescape(str) { const blockTypes = 'blockquote html heading hr list listitem paragraph table tablerow tablecell'.split(' '); +/** + * @param {string} line + * @param {string} lang + */ function extractMeta(line, lang) { try { if (lang === 'html' || lang === 'svelte') { @@ -54,6 +61,7 @@ function extractMeta(line, lang) { } // https://github.com/darkskyapp/string-hash/blob/master/index.js +/** @param {string} str */ function getHash(str) { let hash = 5381; let i = str.length; @@ -64,7 +72,14 @@ function getHash(str) { const demos = new Map(); +/** + * @typedef {{ meta: Record, lang: string, source: string }} CodeBlock + * @typedef {{ id: number, blocks: CodeBlock[] }} CodeGroup + */ + +/** @param {boolean} [returnHtml=true] */ export default function (returnHtml = true) { + /** @type {Record} */ const store = {}; return fs .readdirSync(`src/content/guide`) @@ -74,7 +89,9 @@ export default function (returnHtml = true) { const { content, metadata } = processMarkdown(markdown); + /** @type {CodeGroup[]} */ const groups = []; + /** @type {CodeGroup | null} */ let group = null; let uid = 1; @@ -206,16 +223,17 @@ export default function (returnHtml = true) { let html = marked.marked(content, { async: false }); + /** @type {Record} */ const hashes = {}; groups.forEach(group => { const main = group.blocks[0]; if (main.meta.repl === false) return; - const hash = getHash(group.blocks.map(block => block.source).join('')); + const hash = getHash(group.blocks.map(/** @param {CodeBlock} block */ block => block.source).join('')); hashes[group.id] = hash; - const json5 = group.blocks.find(block => block.lang === 'json'); + const json5 = group.blocks.find(/** @param {CodeBlock} block */ block => block.lang === 'json'); const title = main.meta.title; // if (!title) console.error(`Missing title for demo in ${file}`); @@ -225,8 +243,8 @@ export default function (returnHtml = true) { JSON.stringify({ title: title || 'Example from guide', components: group.blocks - .filter(block => block.lang === 'html' || block.lang === 'js') - .map(block => { + .filter(/** @param {CodeBlock} block */ block => block.lang === 'html' || block.lang === 'js') + .map(/** @param {CodeBlock} block */ block => { const [name, type] = (block.meta.filename || '').split('.'); return { name: name || 'App', @@ -240,8 +258,10 @@ export default function (returnHtml = true) { }); // When extracting sidebar subsections, strip ... from the title for anchors, but keep the display text for the heading itself + /** @type {{ slug: string, title: string }[]} */ const subsections = []; const pattern = /

(.+?)<\/h3>/g; + /** @type {RegExpExecArray | null} */ let match; while ((match = pattern.exec(html))) { @@ -291,7 +311,10 @@ export default function (returnHtml = true) { } return { - html: returnHtml === true ? html.replace(/@@(\d+)/g, (m, id) => hashes[id] || m) : null, + html: + returnHtml === true + ? html.replace(/@@(\d+)/g, (m, id) => hashes[Number(id)] || m) + : null, metadata, subsections, slug: file.replace(/^\d+-/, '').replace(/\.md$/, ''), diff --git a/src/_modules/hljsDefineSvelte.js b/src/_modules/hljsDefineSvelte.js index 8263a02f6..da9ca980f 100644 --- a/src/_modules/hljsDefineSvelte.js +++ b/src/_modules/hljsDefineSvelte.js @@ -1,6 +1,7 @@ /* -------------------------------------------- * Adapted to work as es6 module from https://github.com/AlexxNB/highlightjs-svelte */ +/** @param {any} hljs */ export default function hljsDefineSvelte(hljs) { return { subLanguage: 'xml', diff --git a/src/_modules/processMarkdown.js b/src/_modules/processMarkdown.js index 99310b94a..df972f963 100644 --- a/src/_modules/processMarkdown.js +++ b/src/_modules/processMarkdown.js @@ -1,3 +1,7 @@ +/** + * @param {string} markdown + * @returns {{ metadata: Record, content: string }} + */ export default function processMarkdown(markdown) { const match = /---\n([\s\S]+?)\n---/.exec(markdown); @@ -8,6 +12,7 @@ export default function processMarkdown(markdown) { const frontMatter = match[1]; const content = markdown.slice(match[0].length); + /** @type {Record} */ const metadata = {}; frontMatter.split('\n').forEach(pair => { const colonIndex = pair.indexOf(':'); diff --git a/src/_modules/slugify.js b/src/_modules/slugify.js index 644d4fb4d..d9b5223dd 100644 --- a/src/_modules/slugify.js +++ b/src/_modules/slugify.js @@ -2,6 +2,12 @@ import emoji from 'emoji-regex'; const whitespace = /\s/g; +/** + * @param {string} string + * @param {boolean|null|undefined} maintainCase + * @param {Record} store + * @returns {string} + */ export default function slugger(string, maintainCase, store) { const re = /[\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,./:;<=>?@[\]^`{|}~]/g; const replacement = '-'; diff --git a/src/routes/_site-components/DownloadBtn.svelte b/src/routes/_site-components/DownloadBtn.svelte index 49403ae24..61fc3621c 100644 --- a/src/routes/_site-components/DownloadBtn.svelte +++ b/src/routes/_site-components/DownloadBtn.svelte @@ -16,22 +16,33 @@ let downloading = $state(false); + /** @param {string} [file] */ function getImports(file = '') { const match = file.match(/from\s'(.+)'?/gm) || []; - const imports = match.map(d => d.replace(/(from |'|"|;)/g, '')).filter(d => !d.startsWith('.')); + const imports = match + .map(/** @param {string} d */ d => d.replace(/(from |'|"|;)/g, '')) + .filter(/** @param {string} d */ d => !d.startsWith('.')); return imports; } const imports = [data.main, ...data.components, ...data.componentComponents] - .reduce((store, val) => store.concat(getImports(val.contents)), []) - .reduce((store, val) => { - if (!store.includes(val)) { - store.push(val); - return store; - } else { - return store; - } - }, []); + .reduce( + /** @param {string[]} store @param {{ contents: string }} val */ + (store, val) => store.concat(getImports(val.contents)), + /** @type {string[]} */ ([]) + ) + .reduce( + /** @param {string[]} store @param {string} val */ + (store, val) => { + if (!store.includes(val)) { + store.push(val); + return store; + } else { + return store; + } + }, + /** @type {string[]} */ ([]) + ); async function download() { downloading = true; @@ -40,13 +51,18 @@ const cacheBust = new Date().getTime(); const files = await (await window.fetch(`/svelte-app.json?${cacheBust}`)).json(); + /** @type {Record} */ const depsLookup = await (await window.fetch(`/deps.json?${cacheBust}`)).json(); if (imports.length > 0) { - const idx = files.findIndex(({ path }) => path === 'package.json'); + const idx = files.findIndex( + /** @param {{ path: string }} file */ ({ path }) => path === 'package.json' + ); const pkg = JSON.parse(files[idx].data); + /** @type {Record} */ const deps = {}; + /** @type {Record} */ const devDeps = {}; - imports.forEach(mod => { + imports.forEach(/** @param {string} mod */ mod => { if (mod === 'svelte') { return; } else { @@ -62,37 +78,37 @@ } files.push( - ...data.components.map(component => ({ + ...data.components.map(/** @param {any} component */ component => ({ path: `src/routes/${component.title.replace('./', '')}`, data: component.contents })) ); files.push( - ...data.modules.map(mod => ({ + ...data.modules.map(/** @param {any} mod */ mod => ({ path: `src/routes/${mod.title.replace('./', '')}`, data: mod.contents })) ); files.push( - ...data.componentModules.map(mod => ({ + ...data.componentModules.map(/** @param {any} mod */ mod => ({ path: `src/routes/${mod.title.replace('../', '')}`, data: mod.contents })) ); files.push( - ...data.componentComponents.map(mod => ({ + ...data.componentComponents.map(/** @param {any} mod */ mod => ({ path: `src/routes/${mod.title}`, data: mod.contents })) ); files.push( - ...data.csvs.map(mod => ({ + ...data.csvs.map(/** @param {any} mod */ mod => ({ path: `src/routes/${mod.title.replace('../', '')}`, data: mod.contents })) ); files.push( - ...data.jsons.map(mod => ({ + ...data.jsons.map(/** @param {any} mod */ mod => ({ path: `src/routes/${mod.title.replace('../', '')}`, data: mod.contents })) diff --git a/src/routes/_site-components/DownloadComponentBtn.svelte b/src/routes/_site-components/DownloadComponentBtn.svelte index 438dd80b8..5cf680ce7 100644 --- a/src/routes/_site-components/DownloadComponentBtn.svelte +++ b/src/routes/_site-components/DownloadComponentBtn.svelte @@ -59,7 +59,7 @@ // } // files.push(...data.components.map(component => ({ path: `src/${component.title.replace('./', '')}`, data: component.contents }))); files.push( - ...data.modules.map(mod => ({ path: mod.slug.replace('./', ''), data: mod.contents })) + ...data.modules.map(/** @param {any} mod */ mod => ({ path: mod.slug.replace('./', ''), data: mod.contents })) ); // files.push(...data.componentModules.map(mod => ({ path: `src/${mod.title.replace('../', '')}`, data: mod.contents }))); // files.push(...data.componentComponents.map(mod => ({ path: `src/${mod.title}`, data: mod.contents }))); diff --git a/src/routes/_site-components/GuideContents.svelte b/src/routes/_site-components/GuideContents.svelte index eddee3968..a456225ae 100644 --- a/src/routes/_site-components/GuideContents.svelte +++ b/src/routes/_site-components/GuideContents.svelte @@ -9,7 +9,7 @@ /** @type {Props} */ let { open = $bindable(false), activeGuideSection = $bindable(), sections = [] } = $props(); - const guideSections = sections.map(section => { + const guideSections = sections.map(/** @param {any} section */ section => { return { metadata: section.metadata, subsections: section.subsections, slug: section.slug }; }); diff --git a/src/routes/_site-components/Nav.svelte b/src/routes/_site-components/Nav.svelte index eebce7d90..a151dbda4 100644 --- a/src/routes/_site-components/Nav.svelte +++ b/src/routes/_site-components/Nav.svelte @@ -34,7 +34,7 @@ let nav = $state(); - const slimName = d => d.split(' (')[0]; + const slimName = /** @param {any} d */ d => d.split(' (')[0]; /** @this {HTMLSelectElement} */ function loadPage() { diff --git a/src/routes/api/guide-sections.json/+server.js b/src/routes/api/guide-sections.json/+server.js index eaf6a23da..7a0a6552d 100644 --- a/src/routes/api/guide-sections.json/+server.js +++ b/src/routes/api/guide-sections.json/+server.js @@ -1,5 +1,6 @@ import getSections from '../../../_modules/getSections.js'; +/** @type {ReturnType | undefined} */ let json; export async function GET() { if (!json || process.env.NODE_ENV !== 'production') { diff --git a/src/routes/components/+page.svelte b/src/routes/components/+page.svelte index 27b070802..fd3176abd 100644 --- a/src/routes/components/+page.svelte +++ b/src/routes/components/+page.svelte @@ -3,52 +3,65 @@ import svelteComponents from '../_components.js'; + /** + * @param {string} name + * @returns {string[]} + */ function getClasses(name) { - const parts = name.split('.').filter(d => d !== 'svelte'); + const parts = name.split('.').filter(/** @param {string} d */ d => d !== 'svelte'); parts.shift(); if (parts.length === 0) return ['svg']; return parts; } - const componentGroups = svelteComponents.map(d => { + const componentGroups = svelteComponents.map((/** @type {any} */ d) => { return { - name: `${d.name.replace(/^\w/, w => w.toUpperCase())} components`, - components: sortBy(d.components, 'slug').map(({ name, slug, component }) => { - const classes = getClasses(slug); - return { - name, - slug, - component, - classes, - group: classes.filter(d => d !== 'percent-range')[0] - }; - }) + name: `${d.name.replace(/^\w/, /** @param {string} w */ w => w.toUpperCase())} components`, + components: sortBy(d.components, 'slug').map( + /** @param {{ name: string, slug: string, component: any }} args */ + ({ name, slug, component }) => { + const classes = getClasses(slug); + return { + name, + slug, + component, + classes, + group: classes.filter(/** @param {string} d */ d => d !== 'percent-range')[0] + }; + } + ) }; }); + /** @param {string} name */ function formatName(name) { return name.split('.')[0]; } + /** @param {string} subgroup */ function formatSubgroup(subgroup) { if (subgroup == 'webgl') return 'WebGL'; if (subgroup == 'canvas') return 'Canvas'; return subgroup.toUpperCase(); } + /** @param {string} name */ function slugify(name) { return name.toLowerCase().split(' ')[0]; } + /** @type {HTMLElement | undefined} */ let container; + /** @type {number[]} */ let positions = []; let lastId = 'axis'; let activeSection = $state('axis'); + /** @type {HTMLElement[]} */ let anchors = []; $effect(() => { - if (typeof window !== 'undefined') { - anchors = container.querySelectorAll('[id]'); + if (typeof window !== 'undefined' && container) { + anchors = /** @type {HTMLElement[]} */ ([...container.querySelectorAll('[id]')]); lastId = window.location.hash.slice(1); activeSection = lastId || 'axis'; @@ -60,12 +73,9 @@ function onresize() { if (container) { const { top } = container.getBoundingClientRect(); - positions = [].map.call( - anchors, - /** @param {HTMLAnchorElement} anchor */ anchor => { - return anchor.getBoundingClientRect().top - top; - } - ); + positions = anchors.map(anchor => { + return anchor.getBoundingClientRect().top - top; + }); } } @@ -156,7 +166,7 @@ {#each componentGroups as componentGroup}

{componentGroup.name}

- {#each Object.entries(groupBy(componentGroup.components, d => d.group)) as [subgroup, items]} + {#each Object.entries(groupBy(componentGroup.components, (/** @type {any} */ d) => d.group)) as [subgroup, items]}

{formatSubgroup(subgroup)}

{#each items as item} @@ -167,7 +177,7 @@ > {@html item.classes - .map(d => `${d.replace('percent-', '%-')}`) + .map((/** @type {any} */ d) => `${d.replace('percent-', '%-')}`) .join('')}
diff --git a/src/routes/components/[slug].json/+server.js b/src/routes/components/[slug].json/+server.js index 435c29661..bfc8c8a0d 100644 --- a/src/routes/components/[slug].json/+server.js +++ b/src/routes/components/[slug].json/+server.js @@ -1,8 +1,8 @@ import { error, json } from '@sveltejs/kit'; -import { readFileSync, existsSync } from 'fs'; -import { readdirFilterSync } from 'indian-ocean'; +import { readFileSync, existsSync, readdirSync } from 'fs'; import parseJsdoc from '$lib/helpers/parseJsdoc.js'; +/** @param {string} str */ function cleanMain(str) { const cleaned = str .replace(/\t/g, ' ') @@ -12,18 +12,31 @@ function cleanMain(str) { return cleaned; } +/** @param {string} str */ function cleanContents(str) { return str.replace(/\t/g, ' ').trim(); } +/** @param {string} example */ function getJsPaths(example) { const match = example.match(/\.\/.+\.js('|")/gm); if (match) { - return match.map(d => d.replace('../../', '').replace(/('|")/g, '')); + return match.map(/** @param {string} d */ d => d.replace('../../', '').replace(/('|")/g, '')); } return []; } +/** + * @param {string} dir + * @returns {string[]} + */ +function readdirFilterSync(dir) { + return readdirSync(dir) + .filter(/** @param {string} name */ name => name.endsWith('.svelte')) + .map(/** @param {string} name */ name => `${dir}/${name}`); +} + +/** @type {import('@sveltejs/kit').RequestHandler} */ export async function GET({ params }) { // the `slug` parameter is available because // this file is called [slug].json.js @@ -39,7 +52,7 @@ export async function GET({ params }) { const fromMain = cleanMain(component); - const modules = getJsPaths(component).map(d => { + const modules = getJsPaths(component).map(/** @param {string} d */ d => { return { slug: d.replace('../', ''), contents: cleanContents(readFileSync(d.replace('./', 'src/'), 'utf-8')) @@ -58,7 +71,7 @@ export async function GET({ params }) { const usedIn = examplePaths.map((d, i) => { return { group: i === 0 ? 'Regular' : 'SSR', - matches: readdirFilterSync(d, { fullPath: true }) + matches: readdirFilterSync(d) .map(q => { return { path: q, @@ -69,7 +82,7 @@ export async function GET({ params }) { return q.contents.includes(slug); }) .map(q => { - const name = q.path.split('/').pop().replace('.svelte', ''); + const name = q.path.split('/').pop()?.replace('.svelte', '') ?? ''; return `/example${i === 1 ? '-ssr' : ''}/${name}`; }) }; @@ -84,21 +97,22 @@ export async function GET({ params }) { // fields of other typedefs (annotation configs, arrow configs etc...) don't // show up as component props const commentBlocks = fromMain.match(/\/\*\*[^]*?\*\//g) || []; - const propsBlock = commentBlocks.find(block => block.includes('@typedef {Object} Props')) || ''; + const propsBlock = commentBlocks.find(/** @param {string} block */ block => block.includes('@typedef {Object} Props')) || ''; const jsdocPropertyMatches = propsBlock.matchAll(/(@property [^\n]*)/gm); const propertiesDefaultValues = fromMain.match(/let\s+\{([\s\S]*?)\} = \$props/m); + /** @type {Record} */ let defaultValues = {}; if (propertiesDefaultValues) { // split at comma, but not in parens of function parameters defaultValues = propertiesDefaultValues[1] .split(/,(?![^(]*\))/g) - .map(i => i.trim()) - .filter(i => i !== 'children') + .map(/** @param {string} i */ i => i.trim()) + .filter(/** @param {string} i */ i => i !== 'children') .reduce((acc, item) => { const [key, value] = item.split(' = '); acc[key] = value; return acc; - }, {}); + }, /** @type {Record} */ ({})); } const jsdocParsed = [...jsdocPropertyMatches] @@ -109,7 +123,7 @@ export async function GET({ params }) { parsed['defaultValue'] = defaultValues[parsed['name']]?.replace('$bindable()', ''); return parsed; }) - .filter(i => i !== null); + .filter(/** @param {any} i */ i => i !== null); const response = { main, diff --git a/src/routes/components/[slug]/+page.svelte b/src/routes/components/[slug]/+page.svelte index bdb3fee39..197c1245c 100644 --- a/src/routes/components/[slug]/+page.svelte +++ b/src/routes/components/[slug]/+page.svelte @@ -18,10 +18,15 @@ let active = $derived(data.active); + /** @param {string} text */ function markdownToHtml(text) { return md.render(text); } + /** + * @param {string} str + * @param {string} s + */ function highlight(str, s) { const parts = s.split('.'); let ext = parts[parts.length - 1]; @@ -33,28 +38,31 @@ const lookup = new Map(); components - .flatMap(d => d.components) - .forEach(d => { + .flatMap(/** @param {any} d */ d => d.components) + .forEach(/** @param {any} d */ d => { lookup.set(d.slug, d); }); let component = $derived(lookup.get(data.slug)); + /** @param {string} type */ function printTypes(type) { if (type.includes('|')) { const escaped = type .split('|') - .map(d => `\`${d}\``) + .map(/** @param {string} d */ d => `\`${d}\``) .join(' | '); return `(${escaped})`; } else return `\`${type}\``; } + /** @param {string|undefined} def */ function printDefault(def) { if (!def) return 'None'; return `\`${def}\``; } + /** @param {boolean|undefined} required */ function printRequired(required) { const str = required ? 'yes' : 'no'; return `
${str}
`; @@ -69,7 +77,7 @@ if (data.content.hasjsDoctable === true) { jsdocTableBody = `${data.content.jsdocParsed .map( - d => + /** @param {any} d */ d => `**${d.name}** ${printTypes(d.type)}|${printDefault(d.defaultValue)}|${printRequired( d.required )}|${d.description?.replace(/^(-|–|—)/g, '').trim()}` diff --git a/src/routes/example-ssr/[slug].json/+server.js b/src/routes/example-ssr/[slug].json/+server.js index 104b7b47a..c6846c6d9 100644 --- a/src/routes/example-ssr/[slug].json/+server.js +++ b/src/routes/example-ssr/[slug].json/+server.js @@ -1,38 +1,44 @@ import { error, json } from '@sveltejs/kit'; import * as fs from 'fs'; +/** @param {string} example */ function getComponentJsPaths(example) { return example.match(/\.\.\/.+\.js/gm); } +/** @param {string} str */ function cleanContents(str) { return str.replace(/\t/g, ' ').trim(); } +/** @param {string} example */ function getJsonPaths(example) { const match = example.match(/\.\/.+\.json/gm); if (match) { - return match.map(d => d.replace('../../', '')); + return match.map(/** @param {string} d */ d => d.replace('../../', '')); } return []; } +/** @param {string} example */ function getJsPaths(example) { const match = example.match(/\.\/.+\.js('|")/gm); if (match) { - return match.map(d => d.replace('../../', '').replace(/('|")/g, '')); + return match.map(/** @param {string} d */ d => d.replace('../../', '').replace(/('|")/g, '')); } return []; } +/** @param {string} example */ function getCsvPaths(example) { const match = example.match(/\.\/.+\.csv/gm); if (match) { - return match.map(d => d.replace('../../', '')); + return match.map(/** @param {string} d */ d => d.replace('../../', '')); } return []; } +/** @param {string} example */ function cleanMain(example) { const cleaned = example .replace(/\t/g, ' ') @@ -42,14 +48,16 @@ function cleanMain(example) { return cleaned; } +/** @param {string} example */ function getComponentPaths(example) { const match = example.match(/\.?\.\/.+svelte/gm); if (match) { - return match.map(d => d.replace('../../', '')); + return match.map(/** @param {string} d */ d => d.replace('../../', '')); } return []; } +/** @type {import('@sveltejs/kit').RequestHandler} */ export async function GET({ params }) { // the `slug` parameter is available because // this file is called [slug].json.js @@ -73,28 +81,28 @@ export async function GET({ params }) { const dekPath = `src/content/examples-ssr/${slug}.md`; const dek = fs.existsSync(dekPath) ? fs.readFileSync(dekPath, 'utf-8') : ''; - const components = getComponentPaths(example).map(d => { + const components = getComponentPaths(example).map(/** @param {string} d */ d => { return { title: `./${d}`, contents: cleanContents(fs.readFileSync(`src/${d}`, 'utf-8')) }; }); - const modules = getJsPaths(example).map(d => { + const modules = getJsPaths(example).map(/** @param {string} d */ d => { return { title: d.replace('../', ''), contents: cleanContents(fs.readFileSync(d.replace('../', 'src/'), 'utf-8')) }; }); - const jsons = getJsonPaths(example).map(d => { + const jsons = getJsonPaths(example).map(/** @param {string} d */ d => { return { title: d.replace('../', ''), contents: cleanContents(fs.readFileSync(d.replace('../', 'src/'), 'utf-8')) }; }); - const csvs = getCsvPaths(example).map(d => { + const csvs = getCsvPaths(example).map(/** @param {string} d */ d => { return { title: d.replace('../', ''), contents: cleanContents(fs.readFileSync(d.replace('../', 'src/'), 'utf-8')) @@ -105,7 +113,7 @@ export async function GET({ params }) { const componentModules = componentModulesMatches === null ? [] - : componentModulesMatches.map(d => { + : componentModulesMatches.map(/** @param {string} d */ d => { return { title: d.replace('../', './'), contents: cleanContents(fs.readFileSync(d.replace('../', 'src/'), 'utf-8')) @@ -116,7 +124,7 @@ export async function GET({ params }) { const componentComponents = componentComponentMatches === null ? [] - : componentComponentMatches.map(d => { + : componentComponentMatches.map(/** @param {string} d */ d => { return { title: d.replace('./', './_components/'), contents: cleanContents(fs.readFileSync(d.replace('./', 'src/_components/'), 'utf-8')) diff --git a/src/routes/example-ssr/[slug]/+page.svelte b/src/routes/example-ssr/[slug]/+page.svelte index 5086b2771..0d5c352f0 100644 --- a/src/routes/example-ssr/[slug]/+page.svelte +++ b/src/routes/example-ssr/[slug]/+page.svelte @@ -23,10 +23,12 @@ let active = $derived(data.active); + /** @param {string} text */ function markdownToHtml(text) { return md.render(text); } + /** @param {string} str @param {string} title */ function highlight(str, title) { const parts = title.split('.'); let ext = parts[parts.length - 1]; diff --git a/src/routes/example/[slug].json/+server.js b/src/routes/example/[slug].json/+server.js index b31a83e2d..782c8f9bc 100644 --- a/src/routes/example/[slug].json/+server.js +++ b/src/routes/example/[slug].json/+server.js @@ -1,38 +1,44 @@ import { error, json } from '@sveltejs/kit'; import * as fs from 'fs'; +/** @param {string} example */ function getComponentJsPaths(example) { return example.match(/\.\.\/.+\.js/gm); } +/** @param {string} str */ function cleanContents(str) { return str.replace(/\t/g, ' ').trim(); } +/** @param {string} example */ function getJsonPaths(example) { const match = example.match(/\.\/.+\.json/gm); if (match) { - return match.map(d => d.replace('../../', '')); + return match.map(/** @param {string} d */ d => d.replace('../../', '')); } return []; } +/** @param {string} example */ function getJsPaths(example) { const match = example.match(/\.\/.+\.js('|")/gm); if (match) { - return match.map(d => d.replace('../../', '').replace(/('|")/g, '')); + return match.map(/** @param {string} d */ d => d.replace('../../', '').replace(/('|")/g, '')); } return []; } +/** @param {string} example */ function getCsvPaths(example) { const match = example.match(/\.\/.+\.csv/gm); if (match) { - return match.map(d => d.replace('../../', '')); + return match.map(/** @param {string} d */ d => d.replace('../../', '')); } return []; } +/** @param {string} example */ function cleanMain(example) { const cleaned = example .replace(/\t/g, ' ') @@ -42,10 +48,11 @@ function cleanMain(example) { return cleaned; } +/** @param {string} example */ function getComponentPaths(example) { const match = example.match(/\.?\.\/.+svelte/gm); if (match) { - return match.map(d => d.replace('../../', '')); + return match.map(/** @param {string} d */ d => d.replace('../../', '')); } return []; } @@ -74,28 +81,28 @@ export async function GET({ params }) { const dekPath = `src/content/examples/${slug}.md`; const dek = fs.existsSync(dekPath) ? fs.readFileSync(dekPath, 'utf-8') : ''; - const components = getComponentPaths(example).map(d => { + const components = getComponentPaths(example).map(/** @param {string} d */ d => { return { title: `./${d}`, contents: cleanContents(fs.readFileSync(`src/${d}`, 'utf-8')) }; }); - const modules = getJsPaths(example).map(d => { + const modules = getJsPaths(example).map(/** @param {string} d */ d => { return { title: d.replace('../', ''), contents: cleanContents(fs.readFileSync(d.replace('../', 'src/'), 'utf-8')) }; }); - const jsons = getJsonPaths(example).map(d => { + const jsons = getJsonPaths(example).map(/** @param {string} d */ d => { return { title: d.replace('../', ''), contents: cleanContents(fs.readFileSync(d.replace('../', 'src/'), 'utf-8')) }; }); - const csvs = getCsvPaths(example).map(d => { + const csvs = getCsvPaths(example).map(/** @param {string} d */ d => { return { title: d.replace('../', ''), contents: cleanContents(fs.readFileSync(d.replace('./../', 'src/'), 'utf-8')) @@ -106,7 +113,7 @@ export async function GET({ params }) { const componentModules = componentModulesMatches === null ? [] - : componentModulesMatches.map(d => { + : componentModulesMatches.map(/** @param {string} d */ d => { return { title: d.replace('../', './'), contents: cleanContents(fs.readFileSync(d.replace('../', 'src/'), 'utf-8')) @@ -117,7 +124,7 @@ export async function GET({ params }) { const componentComponents = componentComponentMatches === null ? [] - : componentComponentMatches.map(d => { + : componentComponentMatches.map(/** @param {string} d */ d => { // console.log('d', d, d.replace('./', './_components/')); return { title: d.replace('./', './_components/'), diff --git a/src/routes/example/[slug]/+page.svelte b/src/routes/example/[slug]/+page.svelte index 04a3ed40b..49a198a86 100644 --- a/src/routes/example/[slug]/+page.svelte +++ b/src/routes/example/[slug]/+page.svelte @@ -25,6 +25,7 @@ * @param {string} text - The markdown text to convert. * @returns {string} The converted HTML. */ + /** @param {string} text */ function markdownToHtml(text) { return md.render(text); } @@ -34,6 +35,7 @@ * @param {string} title * @returns {string} highlighted code */ + /** @param {string} str @param {string} title */ function highlight(str, title) { const parts = title.split('.'); let ext = parts[parts.length - 1]; diff --git a/src/routes/guide.json/+server.js b/src/routes/guide.json/+server.js index 5facb1a5c..0b9c2345c 100644 --- a/src/routes/guide.json/+server.js +++ b/src/routes/guide.json/+server.js @@ -1,6 +1,7 @@ import { json } from '@sveltejs/kit'; import getSections from '../../_modules/getSections.js'; +/** @type {ReturnType | undefined} */ let data; export async function GET() { diff --git a/src/routes/guide/+page.svelte b/src/routes/guide/+page.svelte index a1bc4725e..1b1bf7f34 100644 --- a/src/routes/guide/+page.svelte +++ b/src/routes/guide/+page.svelte @@ -4,15 +4,18 @@ /** @type {import('./$types').PageProps} */ let { data } = $props(); + /** @type {HTMLElement | undefined} */ let container; + /** @type {number[]} */ let positions = []; let lastId = 'introduction'; let activeGuideSection = $state(); + /** @type {HTMLElement[]} */ let anchors = []; $effect(() => { - if (typeof window !== 'undefined') { - anchors = container.querySelectorAll('[id]'); + if (typeof window !== 'undefined' && container) { + anchors = /** @type {HTMLElement[]} */ ([...container.querySelectorAll('[id]')]); lastId = window.location.hash.slice(1); activeGuideSection = lastId; @@ -24,12 +27,9 @@ function onresize() { if (container) { const { top } = container.getBoundingClientRect(); - positions = [].map.call( - anchors, - /** @param {HTMLAnchorElement} anchor */ anchor => { - return anchor.getBoundingClientRect().top - top; - } - ); + positions = anchors.map(anchor => { + return anchor.getBoundingClientRect().top - top; + }); } } @@ -72,7 +72,7 @@ {#each data.sections as section}
  • - {section.slug.replace(/^\w/, d => d.toUpperCase()).replaceAll('-', ' ')}- {section.slug.replace(/^\w/, /** @param {string} d */ d => d.toUpperCase()).replaceAll('-', ' ')}
  • {/each} From 670364a860701800ce83eb0c51e0f41a8a5e7c40 Mon Sep 17 00:00:00 2001 From: Michael Keller Date: Fri, 17 Jul 2026 19:28:24 -0400 Subject: [PATCH 02/12] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/routes/example-ssr/[slug]/+page.svelte | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/routes/example-ssr/[slug]/+page.svelte b/src/routes/example-ssr/[slug]/+page.svelte index 0d5c352f0..89cc1ead1 100644 --- a/src/routes/example-ssr/[slug]/+page.svelte +++ b/src/routes/example-ssr/[slug]/+page.svelte @@ -28,7 +28,11 @@ return md.render(text); } - /** @param {string} str @param {string} title */ + /** + * @param {string} str + * @param {string} title + * @returns {string} + */ function highlight(str, title) { const parts = title.split('.'); let ext = parts[parts.length - 1]; From a9e1af632da2a66f7f1fa8ccfeacdc178fd0f9c7 Mon Sep 17 00:00:00 2001 From: Michael Keller Date: Fri, 17 Jul 2026 19:28:58 -0400 Subject: [PATCH 03/12] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- scripts/check-site.js | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/scripts/check-site.js b/scripts/check-site.js index 6b9e97026..9ea2ba2a0 100644 --- a/scripts/check-site.js +++ b/scripts/check-site.js @@ -13,25 +13,20 @@ const svelteCheckBin = require.resolve('svelte-check/bin/svelte-check'); /** Paths deferred to a later pass — not part of this site-only check. */ const ignoredPathParts = [ - '/src/lib/', - '\\src\\lib\\', - '/src/_components/', - '\\src\\_components\\', - '/src/routes/_components/', - '\\src\\routes\\_components\\', - '/src/routes/_components_ssr/', - '\\src\\routes\\_components_ssr\\', - '/src/routes/_examples/', - '\\src\\routes\\_examples\\', - '/src/routes/_examples_ssr/', - '\\src\\routes\\_examples_ssr\\' + 'src/lib/', + 'src/_components/', + 'src/routes/_components/', + 'src/routes/_components_ssr/', + 'src/routes/_examples/', + 'src/routes/_examples_ssr/' ]; /** * @param {string} file */ function isIgnored(file) { - return ignoredPathParts.some(part => file.includes(part)); + const normalized = file.replaceAll('\\', '/').replace(/^[A-Za-z]:/, ''); + return ignoredPathParts.some(part => normalized.includes(`/${part}`) || normalized.startsWith(part)); } const result = spawnSync( From 0cbcaa79442b8d52a117c5548fa4138e4daacf22 Mon Sep 17 00:00:00 2001 From: Michael Keller Date: Fri, 17 Jul 2026 19:29:08 -0400 Subject: [PATCH 04/12] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- scripts/check-site.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/check-site.js b/scripts/check-site.js index 9ea2ba2a0..3a3ffee4d 100644 --- a/scripts/check-site.js +++ b/scripts/check-site.js @@ -63,7 +63,7 @@ function flush() { } for (const line of lines) { - const locParts = line.match(/^(\/[^\s:]+):(\d+):(\d+)$/); + const locParts = line.match(/^(.+):(\d+):(\d+)$/); if (locParts) { flush(); currentFile = locParts[1]; From 9e3ffe74855f06321c0469628c710aff995ba00a Mon Sep 17 00:00:00 2001 From: mhkeller Date: Fri, 17 Jul 2026 19:30:03 -0400 Subject: [PATCH 05/12] fix duplicate blocks --- src/routes/example-ssr/[slug]/+page.svelte | 5 ++++- src/routes/example/[slug]/+page.svelte | 2 -- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/routes/example-ssr/[slug]/+page.svelte b/src/routes/example-ssr/[slug]/+page.svelte index 89cc1ead1..f95d0b484 100644 --- a/src/routes/example-ssr/[slug]/+page.svelte +++ b/src/routes/example-ssr/[slug]/+page.svelte @@ -23,7 +23,10 @@ let active = $derived(data.active); - /** @param {string} text */ + /** + * @param {string} text + * @returns {string} + */ function markdownToHtml(text) { return md.render(text); } diff --git a/src/routes/example/[slug]/+page.svelte b/src/routes/example/[slug]/+page.svelte index 49a198a86..04a3ed40b 100644 --- a/src/routes/example/[slug]/+page.svelte +++ b/src/routes/example/[slug]/+page.svelte @@ -25,7 +25,6 @@ * @param {string} text - The markdown text to convert. * @returns {string} The converted HTML. */ - /** @param {string} text */ function markdownToHtml(text) { return md.render(text); } @@ -35,7 +34,6 @@ * @param {string} title * @returns {string} highlighted code */ - /** @param {string} str @param {string} title */ function highlight(str, title) { const parts = title.split('.'); let ext = parts[parts.length - 1]; From 1f507a1b718ad610825ff16f37a2767de30df18d Mon Sep 17 00:00:00 2001 From: mhkeller Date: Fri, 17 Jul 2026 19:31:57 -0400 Subject: [PATCH 06/12] more duplicates --- src/_modules/arrowUtils.js | 8 ++------ src/_modules/hljsDefineSvelte.js | 4 ++-- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/_modules/arrowUtils.js b/src/_modules/arrowUtils.js index fe343d5a0..d07dc514d 100644 --- a/src/_modules/arrowUtils.js +++ b/src/_modules/arrowUtils.js @@ -1,6 +1,6 @@ // Helper functions for creating swoopy arrows -/* -------------------------------------------- +/** * parseCssValue * * Parse various inputs and return then as a number @@ -8,8 +8,6 @@ * A percentage, which will take the percent of the appropriate dimentions * A pixel value, which will parse as a number * - */ -/** * @param {string|number|null|undefined} d * @param {number} i * @param {number} width @@ -27,14 +25,12 @@ export function parseCssValue(d, i, width, height) { return +d.replace('px', ''); } -/* -------------------------------------------- +/** * getElPosition * * Constract a bounding box relative in our coordinate space * that we can attach arrow starting points to * - */ -/** * @param {Element} el * @returns {{ top: number, right: number, bottom: number, left: number, width: number, height: number }} */ diff --git a/src/_modules/hljsDefineSvelte.js b/src/_modules/hljsDefineSvelte.js index da9ca980f..4d3d4442a 100644 --- a/src/_modules/hljsDefineSvelte.js +++ b/src/_modules/hljsDefineSvelte.js @@ -1,7 +1,7 @@ -/* -------------------------------------------- +/** * Adapted to work as es6 module from https://github.com/AlexxNB/highlightjs-svelte + * @param {any} hljs */ -/** @param {any} hljs */ export default function hljsDefineSvelte(hljs) { return { subLanguage: 'xml', From f77931d9c08a7e0478d709e41a05e4f4eb456ef4 Mon Sep 17 00:00:00 2001 From: mhkeller Date: Fri, 17 Jul 2026 19:32:29 -0400 Subject: [PATCH 07/12] format --- scripts/check-site.js | 13 ++- src/_modules/getSections.js | 35 +++--- .../_site-components/DownloadBtn.svelte | 104 ++++++++++-------- .../DownloadComponentBtn.svelte | 4 +- .../_site-components/GuideContents.svelte | 8 +- src/routes/components/+page.svelte | 5 +- src/routes/components/[slug].json/+server.js | 19 ++-- src/routes/components/[slug]/+page.svelte | 8 +- src/routes/example-ssr/[slug].json/+server.js | 90 ++++++++------- src/routes/example/[slug].json/+server.js | 92 +++++++++------- src/routes/guide/+page.svelte | 4 +- src/scripts/svelte-app/.github/dependabot.yml | 6 +- src/scripts/svelte-app/src/app.css | 3 +- 13 files changed, 225 insertions(+), 166 deletions(-) diff --git a/scripts/check-site.js b/scripts/check-site.js index 3a3ffee4d..814e660bf 100644 --- a/scripts/check-site.js +++ b/scripts/check-site.js @@ -29,11 +29,10 @@ function isIgnored(file) { return ignoredPathParts.some(part => normalized.includes(`/${part}`) || normalized.startsWith(part)); } -const result = spawnSync( - process.execPath, - [svelteCheckBin, '--tsconfig', './jsconfig.site.json'], - { encoding: 'utf8', maxBuffer: 20 * 1024 * 1024 } -); +const result = spawnSync(process.execPath, [svelteCheckBin, '--tsconfig', './jsconfig.site.json'], { + encoding: 'utf8', + maxBuffer: 20 * 1024 * 1024 +}); const output = result.stdout || ''; process.stdout.write(output); @@ -87,7 +86,9 @@ for (const line of lines) { flush(); if (siteErrors.length) { - console.error(`\ncheck:site found ${siteErrors.length} internal-site error(s) (lib/charts ignored).`); + console.error( + `\ncheck:site found ${siteErrors.length} internal-site error(s) (lib/charts ignored).` + ); process.exit(1); } diff --git a/src/_modules/getSections.js b/src/_modules/getSections.js index 838ab33d8..2eec0ddd7 100644 --- a/src/_modules/getSections.js +++ b/src/_modules/getSections.js @@ -230,10 +230,14 @@ export default function (returnHtml = true) { const main = group.blocks[0]; if (main.meta.repl === false) return; - const hash = getHash(group.blocks.map(/** @param {CodeBlock} block */ block => block.source).join('')); + const hash = getHash( + group.blocks.map(/** @param {CodeBlock} block */ block => block.source).join('') + ); hashes[group.id] = hash; - const json5 = group.blocks.find(/** @param {CodeBlock} block */ block => block.lang === 'json'); + const json5 = group.blocks.find( + /** @param {CodeBlock} block */ block => block.lang === 'json' + ); const title = main.meta.title; // if (!title) console.error(`Missing title for demo in ${file}`); @@ -243,15 +247,20 @@ export default function (returnHtml = true) { JSON.stringify({ title: title || 'Example from guide', components: group.blocks - .filter(/** @param {CodeBlock} block */ block => block.lang === 'html' || block.lang === 'js') - .map(/** @param {CodeBlock} block */ block => { - const [name, type] = (block.meta.filename || '').split('.'); - return { - name: name || 'App', - type: type || 'html', - source: block.source - }; - }), + .filter( + /** @param {CodeBlock} block */ block => + block.lang === 'html' || block.lang === 'js' + ) + .map( + /** @param {CodeBlock} block */ block => { + const [name, type] = (block.meta.filename || '').split('.'); + return { + name: name || 'App', + type: type || 'html', + source: block.source + }; + } + ), json5: json5 && json5.source }) ); @@ -312,9 +321,7 @@ export default function (returnHtml = true) { return { html: - returnHtml === true - ? html.replace(/@@(\d+)/g, (m, id) => hashes[Number(id)] || m) - : null, + returnHtml === true ? html.replace(/@@(\d+)/g, (m, id) => hashes[Number(id)] || m) : null, metadata, subsections, slug: file.replace(/^\d+-/, '').replace(/\.md$/, ''), diff --git a/src/routes/_site-components/DownloadBtn.svelte b/src/routes/_site-components/DownloadBtn.svelte index 61fc3621c..e32edb405 100644 --- a/src/routes/_site-components/DownloadBtn.svelte +++ b/src/routes/_site-components/DownloadBtn.svelte @@ -27,22 +27,18 @@ const imports = [data.main, ...data.components, ...data.componentComponents] .reduce( - /** @param {string[]} store @param {{ contents: string }} val */ - (store, val) => store.concat(getImports(val.contents)), + (/** @type {string[]} */ store, /** @type {{ contents: string }} */ val) => + store.concat(getImports(val.contents)), /** @type {string[]} */ ([]) ) - .reduce( - /** @param {string[]} store @param {string} val */ - (store, val) => { - if (!store.includes(val)) { - store.push(val); - return store; - } else { - return store; - } - }, - /** @type {string[]} */ ([]) - ); + .reduce((/** @type {string[]} */ store, /** @type {string} */ val) => { + if (!store.includes(val)) { + store.push(val); + return store; + } else { + return store; + } + }, /** @type {string[]} */ ([])); async function download() { downloading = true; @@ -62,56 +58,70 @@ const deps = {}; /** @type {Record} */ const devDeps = {}; - imports.forEach(/** @param {string} mod */ mod => { - if (mod === 'svelte') { - return; - } else { - deps[mod] = depsLookup[mod]; - } - if (!depsLookup[mod]) { - window.alert(`Missing dependency, add "${mod}" to this repo's package.json`); + imports.forEach( + /** @param {string} mod */ mod => { + if (mod === 'svelte') { + return; + } else { + deps[mod] = depsLookup[mod]; + } + if (!depsLookup[mod]) { + window.alert(`Missing dependency, add "${mod}" to this repo's package.json`); + } } - }); + ); Object.assign(pkg.dependencies, deps); Object.assign(pkg.devDependencies, devDeps); files[idx].data = JSON.stringify(pkg, null, ' '); } files.push( - ...data.components.map(/** @param {any} component */ component => ({ - path: `src/routes/${component.title.replace('./', '')}`, - data: component.contents - })) + ...data.components.map( + /** @param {any} component */ component => ({ + path: `src/routes/${component.title.replace('./', '')}`, + data: component.contents + }) + ) ); files.push( - ...data.modules.map(/** @param {any} mod */ mod => ({ - path: `src/routes/${mod.title.replace('./', '')}`, - data: mod.contents - })) + ...data.modules.map( + /** @param {any} mod */ mod => ({ + path: `src/routes/${mod.title.replace('./', '')}`, + data: mod.contents + }) + ) ); files.push( - ...data.componentModules.map(/** @param {any} mod */ mod => ({ - path: `src/routes/${mod.title.replace('../', '')}`, - data: mod.contents - })) + ...data.componentModules.map( + /** @param {any} mod */ mod => ({ + path: `src/routes/${mod.title.replace('../', '')}`, + data: mod.contents + }) + ) ); files.push( - ...data.componentComponents.map(/** @param {any} mod */ mod => ({ - path: `src/routes/${mod.title}`, - data: mod.contents - })) + ...data.componentComponents.map( + /** @param {any} mod */ mod => ({ + path: `src/routes/${mod.title}`, + data: mod.contents + }) + ) ); files.push( - ...data.csvs.map(/** @param {any} mod */ mod => ({ - path: `src/routes/${mod.title.replace('../', '')}`, - data: mod.contents - })) + ...data.csvs.map( + /** @param {any} mod */ mod => ({ + path: `src/routes/${mod.title.replace('../', '')}`, + data: mod.contents + }) + ) ); files.push( - ...data.jsons.map(/** @param {any} mod */ mod => ({ - path: `src/routes/${mod.title.replace('../', '')}`, - data: mod.contents - })) + ...data.jsons.map( + /** @param {any} mod */ mod => ({ + path: `src/routes/${mod.title.replace('../', '')}`, + data: mod.contents + }) + ) ); files.push({ path: `src/routes/+page.svelte`, diff --git a/src/routes/_site-components/DownloadComponentBtn.svelte b/src/routes/_site-components/DownloadComponentBtn.svelte index 5cf680ce7..820de48ed 100644 --- a/src/routes/_site-components/DownloadComponentBtn.svelte +++ b/src/routes/_site-components/DownloadComponentBtn.svelte @@ -59,7 +59,9 @@ // } // files.push(...data.components.map(component => ({ path: `src/${component.title.replace('./', '')}`, data: component.contents }))); files.push( - ...data.modules.map(/** @param {any} mod */ mod => ({ path: mod.slug.replace('./', ''), data: mod.contents })) + ...data.modules.map( + /** @param {any} mod */ mod => ({ path: mod.slug.replace('./', ''), data: mod.contents }) + ) ); // files.push(...data.componentModules.map(mod => ({ path: `src/${mod.title.replace('../', '')}`, data: mod.contents }))); // files.push(...data.componentComponents.map(mod => ({ path: `src/${mod.title}`, data: mod.contents }))); diff --git a/src/routes/_site-components/GuideContents.svelte b/src/routes/_site-components/GuideContents.svelte index a456225ae..251099083 100644 --- a/src/routes/_site-components/GuideContents.svelte +++ b/src/routes/_site-components/GuideContents.svelte @@ -9,9 +9,11 @@ /** @type {Props} */ let { open = $bindable(false), activeGuideSection = $bindable(), sections = [] } = $props(); - const guideSections = sections.map(/** @param {any} section */ section => { - return { metadata: section.metadata, subsections: section.subsections, slug: section.slug }; - }); + const guideSections = sections.map( + /** @param {any} section */ section => { + return { metadata: section.metadata, subsections: section.subsections, slug: section.slug }; + } + ); function close() { open = false; diff --git a/src/routes/components/+page.svelte b/src/routes/components/+page.svelte index fd3176abd..fe0d1def0 100644 --- a/src/routes/components/+page.svelte +++ b/src/routes/components/+page.svelte @@ -177,7 +177,10 @@ > {@html item.classes - .map((/** @type {any} */ d) => `${d.replace('percent-', '%-')}`) + .map( + (/** @type {any} */ d) => + `${d.replace('percent-', '%-')}` + ) .join('')}
    diff --git a/src/routes/components/[slug].json/+server.js b/src/routes/components/[slug].json/+server.js index bfc8c8a0d..3cb0522ba 100644 --- a/src/routes/components/[slug].json/+server.js +++ b/src/routes/components/[slug].json/+server.js @@ -52,12 +52,14 @@ export async function GET({ params }) { const fromMain = cleanMain(component); - const modules = getJsPaths(component).map(/** @param {string} d */ d => { - return { - slug: d.replace('../', ''), - contents: cleanContents(readFileSync(d.replace('./', 'src/'), 'utf-8')) - }; - }); + const modules = getJsPaths(component).map( + /** @param {string} d */ d => { + return { + slug: d.replace('../', ''), + contents: cleanContents(readFileSync(d.replace('./', 'src/'), 'utf-8')) + }; + } + ); const main = { slug, @@ -97,7 +99,10 @@ export async function GET({ params }) { // fields of other typedefs (annotation configs, arrow configs etc...) don't // show up as component props const commentBlocks = fromMain.match(/\/\*\*[^]*?\*\//g) || []; - const propsBlock = commentBlocks.find(/** @param {string} block */ block => block.includes('@typedef {Object} Props')) || ''; + const propsBlock = + commentBlocks.find( + /** @param {string} block */ block => block.includes('@typedef {Object} Props') + ) || ''; const jsdocPropertyMatches = propsBlock.matchAll(/(@property [^\n]*)/gm); const propertiesDefaultValues = fromMain.match(/let\s+\{([\s\S]*?)\} = \$props/m); /** @type {Record} */ diff --git a/src/routes/components/[slug]/+page.svelte b/src/routes/components/[slug]/+page.svelte index 197c1245c..6d5fc34b7 100644 --- a/src/routes/components/[slug]/+page.svelte +++ b/src/routes/components/[slug]/+page.svelte @@ -39,9 +39,11 @@ const lookup = new Map(); components .flatMap(/** @param {any} d */ d => d.components) - .forEach(/** @param {any} d */ d => { - lookup.set(d.slug, d); - }); + .forEach( + /** @param {any} d */ d => { + lookup.set(d.slug, d); + } + ); let component = $derived(lookup.get(data.slug)); diff --git a/src/routes/example-ssr/[slug].json/+server.js b/src/routes/example-ssr/[slug].json/+server.js index c6846c6d9..f4f2174b3 100644 --- a/src/routes/example-ssr/[slug].json/+server.js +++ b/src/routes/example-ssr/[slug].json/+server.js @@ -81,55 +81,67 @@ export async function GET({ params }) { const dekPath = `src/content/examples-ssr/${slug}.md`; const dek = fs.existsSync(dekPath) ? fs.readFileSync(dekPath, 'utf-8') : ''; - const components = getComponentPaths(example).map(/** @param {string} d */ d => { - return { - title: `./${d}`, - contents: cleanContents(fs.readFileSync(`src/${d}`, 'utf-8')) - }; - }); - - const modules = getJsPaths(example).map(/** @param {string} d */ d => { - return { - title: d.replace('../', ''), - contents: cleanContents(fs.readFileSync(d.replace('../', 'src/'), 'utf-8')) - }; - }); - - const jsons = getJsonPaths(example).map(/** @param {string} d */ d => { - return { - title: d.replace('../', ''), - contents: cleanContents(fs.readFileSync(d.replace('../', 'src/'), 'utf-8')) - }; - }); - - const csvs = getCsvPaths(example).map(/** @param {string} d */ d => { - return { - title: d.replace('../', ''), - contents: cleanContents(fs.readFileSync(d.replace('../', 'src/'), 'utf-8')) - }; - }); + const components = getComponentPaths(example).map( + /** @param {string} d */ d => { + return { + title: `./${d}`, + contents: cleanContents(fs.readFileSync(`src/${d}`, 'utf-8')) + }; + } + ); + + const modules = getJsPaths(example).map( + /** @param {string} d */ d => { + return { + title: d.replace('../', ''), + contents: cleanContents(fs.readFileSync(d.replace('../', 'src/'), 'utf-8')) + }; + } + ); + + const jsons = getJsonPaths(example).map( + /** @param {string} d */ d => { + return { + title: d.replace('../', ''), + contents: cleanContents(fs.readFileSync(d.replace('../', 'src/'), 'utf-8')) + }; + } + ); + + const csvs = getCsvPaths(example).map( + /** @param {string} d */ d => { + return { + title: d.replace('../', ''), + contents: cleanContents(fs.readFileSync(d.replace('../', 'src/'), 'utf-8')) + }; + } + ); const componentModulesMatches = getComponentJsPaths(components.map(d => d.contents).join('')); const componentModules = componentModulesMatches === null ? [] - : componentModulesMatches.map(/** @param {string} d */ d => { - return { - title: d.replace('../', './'), - contents: cleanContents(fs.readFileSync(d.replace('../', 'src/'), 'utf-8')) - }; - }); + : componentModulesMatches.map( + /** @param {string} d */ d => { + return { + title: d.replace('../', './'), + contents: cleanContents(fs.readFileSync(d.replace('../', 'src/'), 'utf-8')) + }; + } + ); const componentComponentMatches = getComponentPaths(components.map(d => d.contents).join('')); const componentComponents = componentComponentMatches === null ? [] - : componentComponentMatches.map(/** @param {string} d */ d => { - return { - title: d.replace('./', './_components/'), - contents: cleanContents(fs.readFileSync(d.replace('./', 'src/_components/'), 'utf-8')) - }; - }); + : componentComponentMatches.map( + /** @param {string} d */ d => { + return { + title: d.replace('./', './_components/'), + contents: cleanContents(fs.readFileSync(d.replace('./', 'src/_components/'), 'utf-8')) + }; + } + ); const response = { main, diff --git a/src/routes/example/[slug].json/+server.js b/src/routes/example/[slug].json/+server.js index 782c8f9bc..ebc5d4af5 100644 --- a/src/routes/example/[slug].json/+server.js +++ b/src/routes/example/[slug].json/+server.js @@ -81,56 +81,68 @@ export async function GET({ params }) { const dekPath = `src/content/examples/${slug}.md`; const dek = fs.existsSync(dekPath) ? fs.readFileSync(dekPath, 'utf-8') : ''; - const components = getComponentPaths(example).map(/** @param {string} d */ d => { - return { - title: `./${d}`, - contents: cleanContents(fs.readFileSync(`src/${d}`, 'utf-8')) - }; - }); - - const modules = getJsPaths(example).map(/** @param {string} d */ d => { - return { - title: d.replace('../', ''), - contents: cleanContents(fs.readFileSync(d.replace('../', 'src/'), 'utf-8')) - }; - }); - - const jsons = getJsonPaths(example).map(/** @param {string} d */ d => { - return { - title: d.replace('../', ''), - contents: cleanContents(fs.readFileSync(d.replace('../', 'src/'), 'utf-8')) - }; - }); - - const csvs = getCsvPaths(example).map(/** @param {string} d */ d => { - return { - title: d.replace('../', ''), - contents: cleanContents(fs.readFileSync(d.replace('./../', 'src/'), 'utf-8')) - }; - }); + const components = getComponentPaths(example).map( + /** @param {string} d */ d => { + return { + title: `./${d}`, + contents: cleanContents(fs.readFileSync(`src/${d}`, 'utf-8')) + }; + } + ); + + const modules = getJsPaths(example).map( + /** @param {string} d */ d => { + return { + title: d.replace('../', ''), + contents: cleanContents(fs.readFileSync(d.replace('../', 'src/'), 'utf-8')) + }; + } + ); + + const jsons = getJsonPaths(example).map( + /** @param {string} d */ d => { + return { + title: d.replace('../', ''), + contents: cleanContents(fs.readFileSync(d.replace('../', 'src/'), 'utf-8')) + }; + } + ); + + const csvs = getCsvPaths(example).map( + /** @param {string} d */ d => { + return { + title: d.replace('../', ''), + contents: cleanContents(fs.readFileSync(d.replace('./../', 'src/'), 'utf-8')) + }; + } + ); const componentModulesMatches = getComponentJsPaths(components.map(d => d.contents).join('')); const componentModules = componentModulesMatches === null ? [] - : componentModulesMatches.map(/** @param {string} d */ d => { - return { - title: d.replace('../', './'), - contents: cleanContents(fs.readFileSync(d.replace('../', 'src/'), 'utf-8')) - }; - }); + : componentModulesMatches.map( + /** @param {string} d */ d => { + return { + title: d.replace('../', './'), + contents: cleanContents(fs.readFileSync(d.replace('../', 'src/'), 'utf-8')) + }; + } + ); const componentComponentMatches = getComponentPaths(components.map(d => d.contents).join('')); const componentComponents = componentComponentMatches === null ? [] - : componentComponentMatches.map(/** @param {string} d */ d => { - // console.log('d', d, d.replace('./', './_components/')); - return { - title: d.replace('./', './_components/'), - contents: cleanContents(fs.readFileSync(d.replace('./', 'src/_components/'), 'utf-8')) - }; - }); + : componentComponentMatches.map( + /** @param {string} d */ d => { + // console.log('d', d, d.replace('./', './_components/')); + return { + title: d.replace('./', './_components/'), + contents: cleanContents(fs.readFileSync(d.replace('./', 'src/_components/'), 'utf-8')) + }; + } + ); // console.log(componentComponents); diff --git a/src/routes/guide/+page.svelte b/src/routes/guide/+page.svelte index 1b1bf7f34..65b05bf0f 100644 --- a/src/routes/guide/+page.svelte +++ b/src/routes/guide/+page.svelte @@ -72,7 +72,9 @@ {#each data.sections as section}
  • - {section.slug.replace(/^\w/, /** @param {string} d */ d => d.toUpperCase()).replaceAll('-', ' ')}- {section.slug + .replace(/^\w/, /** @param {string} d */ d => d.toUpperCase()) + .replaceAll('-', ' ')}
  • {/each} diff --git a/src/scripts/svelte-app/.github/dependabot.yml b/src/scripts/svelte-app/.github/dependabot.yml index f71d469b2..7dea44ee5 100644 --- a/src/scripts/svelte-app/.github/dependabot.yml +++ b/src/scripts/svelte-app/.github/dependabot.yml @@ -5,7 +5,7 @@ version: 2 updates: - - package-ecosystem: "npm" # See documentation for possible values - directory: "/" # Location of package manifests + - package-ecosystem: 'npm' # See documentation for possible values + directory: '/' # Location of package manifests schedule: - interval: "monthly" + interval: 'monthly' diff --git a/src/scripts/svelte-app/src/app.css b/src/scripts/svelte-app/src/app.css index 9f5167fd7..9d3b9c4f3 100644 --- a/src/scripts/svelte-app/src/app.css +++ b/src/scripts/svelte-app/src/app.css @@ -10,7 +10,8 @@ body { margin: 0; padding: 0; box-sizing: border-box; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell, + font-family: + -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif; } From dd5b03eec2e9f12c95750c66277d8642a6b515c9 Mon Sep 17 00:00:00 2001 From: mhkeller Date: Fri, 17 Jul 2026 19:34:08 -0400 Subject: [PATCH 08/12] more fixes --- .../_site-components/DownloadBtn.svelte | 1 + .../_site-components/GuideContents.svelte | 1 + src/routes/components/[slug]/+page.svelte | 25 ++++++++++++++++--- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/routes/_site-components/DownloadBtn.svelte b/src/routes/_site-components/DownloadBtn.svelte index e32edb405..14f7c728b 100644 --- a/src/routes/_site-components/DownloadBtn.svelte +++ b/src/routes/_site-components/DownloadBtn.svelte @@ -25,6 +25,7 @@ return imports; } + // svelte-ignore state_referenced_locally const imports = [data.main, ...data.components, ...data.componentComponents] .reduce( (/** @type {string[]} */ store, /** @type {{ contents: string }} */ val) => diff --git a/src/routes/_site-components/GuideContents.svelte b/src/routes/_site-components/GuideContents.svelte index 251099083..ea61dd8ce 100644 --- a/src/routes/_site-components/GuideContents.svelte +++ b/src/routes/_site-components/GuideContents.svelte @@ -9,6 +9,7 @@ /** @type {Props} */ let { open = $bindable(false), activeGuideSection = $bindable(), sections = [] } = $props(); + // svelte-ignore state_referenced_locally const guideSections = sections.map( /** @param {any} section */ section => { return { metadata: section.metadata, subsections: section.subsections, slug: section.slug }; diff --git a/src/routes/components/[slug]/+page.svelte b/src/routes/components/[slug]/+page.svelte index 6d5fc34b7..686ba7c55 100644 --- a/src/routes/components/[slug]/+page.svelte +++ b/src/routes/components/[slug]/+page.svelte @@ -18,7 +18,11 @@ let active = $derived(data.active); - /** @param {string} text */ + /** + * Converts markdown text to HTML. + * @param {string} text - The markdown text to convert. + * @returns {string} The converted HTML. + */ function markdownToHtml(text) { return md.render(text); } @@ -26,6 +30,7 @@ /** * @param {string} str * @param {string} s + * @returns {string} highlighted code */ function highlight(str, s) { const parts = s.split('.'); @@ -47,7 +52,10 @@ let component = $derived(lookup.get(data.slug)); - /** @param {string} type */ + /** + * @param {string} type + * @returns {string} + */ function printTypes(type) { if (type.includes('|')) { const escaped = type @@ -58,13 +66,19 @@ } else return `\`${type}\``; } - /** @param {string|undefined} def */ + /** + * @param {string|undefined} def + * @returns {string} + */ function printDefault(def) { if (!def) return 'None'; return `\`${def}\``; } - /** @param {boolean|undefined} required */ + /** + * @param {boolean|undefined} required + * @returns {string} + */ function printRequired(required) { const str = required ? 'yes' : 'no'; return `
    ${str}
    `; @@ -76,7 +90,9 @@ let jsdocTableBody = ''; let jsdocTable = $state(''); + // svelte-ignore state_referenced_locally if (data.content.hasjsDoctable === true) { + // svelte-ignore state_referenced_locally jsdocTableBody = `${data.content.jsdocParsed .map( /** @param {any} d */ d => @@ -85,6 +101,7 @@ )}|${d.description?.replace(/^(-|–|—)/g, '').trim()}` ) .join('\n')}`; + // svelte-ignore state_referenced_locally jsdocTable = data.content.jsdocParsed.length ? `${jsdocTableHeader}\n${jsdocTableBody}` : ''; } From 6e097bc2d13d638d333fef9a27da9f9ce117c5e4 Mon Sep 17 00:00:00 2001 From: Michael Keller Date: Fri, 17 Jul 2026 19:51:31 -0400 Subject: [PATCH 09/12] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/routes/_site-components/Nav.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/_site-components/Nav.svelte b/src/routes/_site-components/Nav.svelte index a151dbda4..a803da525 100644 --- a/src/routes/_site-components/Nav.svelte +++ b/src/routes/_site-components/Nav.svelte @@ -34,7 +34,7 @@ let nav = $state(); - const slimName = /** @param {any} d */ d => d.split(' (')[0]; + const slimName = /** @param {string} d */ d => d.split(' (')[0]; /** @this {HTMLSelectElement} */ function loadPage() { From d482ba2003132083b5123cec782d96dafeee348f Mon Sep 17 00:00:00 2001 From: Michael Keller Date: Fri, 17 Jul 2026 19:51:37 -0400 Subject: [PATCH 10/12] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/_modules/arrowUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_modules/arrowUtils.js b/src/_modules/arrowUtils.js index d07dc514d..8d165fb4e 100644 --- a/src/_modules/arrowUtils.js +++ b/src/_modules/arrowUtils.js @@ -36,7 +36,7 @@ export function parseCssValue(d, i, width, height) { */ export function getElPosition(el) { const annotationBbox = el.getBoundingClientRect(); - const parentBbox = /** @type {Element} */ (el.parentNode).getBoundingClientRect(); + const parentBbox = (el.parentElement ?? el).getBoundingClientRect(); const coords = { top: annotationBbox.top - parentBbox.top, right: annotationBbox.right - parentBbox.left, From 4bddcc219fa9546c442b2eaa2eb35bcf33e5f38c Mon Sep 17 00:00:00 2001 From: Michael Keller Date: Fri, 17 Jul 2026 19:51:48 -0400 Subject: [PATCH 11/12] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/routes/components/+page.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/components/+page.svelte b/src/routes/components/+page.svelte index fe0d1def0..83836f09d 100644 --- a/src/routes/components/+page.svelte +++ b/src/routes/components/+page.svelte @@ -14,7 +14,7 @@ return parts; } - const componentGroups = svelteComponents.map((/** @type {any} */ d) => { + const componentGroups = svelteComponents.map((/** @type {{ name: string, components: any[] }} */ d) => { return { name: `${d.name.replace(/^\w/, /** @param {string} w */ w => w.toUpperCase())} components`, components: sortBy(d.components, 'slug').map( From 286dad7ff65f6f144a425c315cb933e4138209c2 Mon Sep 17 00:00:00 2001 From: mhkeller Date: Fri, 17 Jul 2026 20:57:51 -0400 Subject: [PATCH 12/12] narrow types --- src/_modules/constructReplLink.js | 23 ++++++++------ src/_modules/getSections.js | 8 +++++ .../_site-components/DownloadBtn.svelte | 25 +++++++++------- .../DownloadComponentBtn.svelte | 22 ++++++++++++-- .../_site-components/GuideContents.svelte | 10 +++++-- src/routes/components/+page.svelte | 26 +++++++++++++--- src/routes/components/[slug].json/+server.js | 21 +++++++++++-- src/routes/components/[slug]/+page.svelte | 30 ++++++++++++++++--- 8 files changed, 131 insertions(+), 34 deletions(-) diff --git a/src/_modules/constructReplLink.js b/src/_modules/constructReplLink.js index 0830f7dac..c2d2c26b8 100644 --- a/src/_modules/constructReplLink.js +++ b/src/_modules/constructReplLink.js @@ -2,17 +2,22 @@ import { csvParse } from 'd3-dsv'; import { compress_and_encode_text } from './createReplHash.js'; +/** + * @typedef {{ title: string, contents: string }} CodeFile + * @typedef {{ + * main: CodeFile, + * components: CodeFile[], + * componentModules: CodeFile[], + * modules: CodeFile[], + * componentComponents: CodeFile[], + * jsons: CodeFile[], + * csvs: CodeFile[] + * }} ExampleContent + */ + /** * @param {string} pageName - * @param {{ - * main: { title: string, contents: string }, - * components: { title: string, contents: string }[], - * componentModules: { title: string, contents: string }[], - * modules: { title: string, contents: string }[], - * componentComponents: { title: string, contents: string }[], - * jsons: { title: string, contents: string }[], - * csvs: { title: string, contents: string }[] - * }} content + * @param {ExampleContent} content */ export default async function constructReplLink(pageName, content) { // TODO, clean up import paths diff --git a/src/_modules/getSections.js b/src/_modules/getSections.js index 2eec0ddd7..297bfb1a5 100644 --- a/src/_modules/getSections.js +++ b/src/_modules/getSections.js @@ -75,6 +75,14 @@ const demos = new Map(); /** * @typedef {{ meta: Record, lang: string, source: string }} CodeBlock * @typedef {{ id: number, blocks: CodeBlock[] }} CodeGroup + * @typedef {{ slug: string, title: string }} GuideSubsection + * @typedef {{ + * html: string | null, + * metadata: Record, + * subsections: GuideSubsection[], + * slug: string, + * file: string + * }} GuideSection */ /** @param {boolean} [returnHtml=true] */ diff --git a/src/routes/_site-components/DownloadBtn.svelte b/src/routes/_site-components/DownloadBtn.svelte index 14f7c728b..117b9c90e 100644 --- a/src/routes/_site-components/DownloadBtn.svelte +++ b/src/routes/_site-components/DownloadBtn.svelte @@ -4,15 +4,20 @@ import downloadBlob from '../../_modules/downloadBlob.js'; + /** + * @typedef {import('../../_modules/constructReplLink.js').CodeFile} CodeFile + * @typedef {import('../../_modules/constructReplLink.js').ExampleContent} ExampleContent + */ + /** * @typedef {Object} Props - * @property {any} [data] - * @property {any} slug + * @property {ExampleContent} [data] + * @property {string} slug * @property {boolean} [ssr] */ /** @type {Props} */ - let { data = {}, slug, ssr = false } = $props(); + let { data = /** @type {ExampleContent} */ ({}), slug, ssr = false } = $props(); let downloading = $state(false); @@ -28,7 +33,7 @@ // svelte-ignore state_referenced_locally const imports = [data.main, ...data.components, ...data.componentComponents] .reduce( - (/** @type {string[]} */ store, /** @type {{ contents: string }} */ val) => + (/** @type {string[]} */ store, /** @type {CodeFile} */ val) => store.concat(getImports(val.contents)), /** @type {string[]} */ ([]) ) @@ -78,7 +83,7 @@ files.push( ...data.components.map( - /** @param {any} component */ component => ({ + /** @param {CodeFile} component */ component => ({ path: `src/routes/${component.title.replace('./', '')}`, data: component.contents }) @@ -86,7 +91,7 @@ ); files.push( ...data.modules.map( - /** @param {any} mod */ mod => ({ + /** @param {CodeFile} mod */ mod => ({ path: `src/routes/${mod.title.replace('./', '')}`, data: mod.contents }) @@ -94,7 +99,7 @@ ); files.push( ...data.componentModules.map( - /** @param {any} mod */ mod => ({ + /** @param {CodeFile} mod */ mod => ({ path: `src/routes/${mod.title.replace('../', '')}`, data: mod.contents }) @@ -102,7 +107,7 @@ ); files.push( ...data.componentComponents.map( - /** @param {any} mod */ mod => ({ + /** @param {CodeFile} mod */ mod => ({ path: `src/routes/${mod.title}`, data: mod.contents }) @@ -110,7 +115,7 @@ ); files.push( ...data.csvs.map( - /** @param {any} mod */ mod => ({ + /** @param {CodeFile} mod */ mod => ({ path: `src/routes/${mod.title.replace('../', '')}`, data: mod.contents }) @@ -118,7 +123,7 @@ ); files.push( ...data.jsons.map( - /** @param {any} mod */ mod => ({ + /** @param {CodeFile} mod */ mod => ({ path: `src/routes/${mod.title.replace('../', '')}`, data: mod.contents }) diff --git a/src/routes/_site-components/DownloadComponentBtn.svelte b/src/routes/_site-components/DownloadComponentBtn.svelte index 820de48ed..80eff2a11 100644 --- a/src/routes/_site-components/DownloadComponentBtn.svelte +++ b/src/routes/_site-components/DownloadComponentBtn.svelte @@ -4,7 +4,22 @@ import downloadBlob from '../../_modules/downloadBlob.js'; - let { data = {}, slug } = $props(); + /** + * @typedef {{ slug: string, contents: string }} ComponentFile + * @typedef {{ + * main: ComponentFile, + * modules: ComponentFile[] + * }} ComponentContent + */ + + /** + * @typedef {Object} Props + * @property {ComponentContent} [data] + * @property {string} slug + */ + + /** @type {Props} */ + let { data = /** @type {ComponentContent} */ ({}), slug } = $props(); let downloading = $state(false); @@ -60,7 +75,10 @@ // files.push(...data.components.map(component => ({ path: `src/${component.title.replace('./', '')}`, data: component.contents }))); files.push( ...data.modules.map( - /** @param {any} mod */ mod => ({ path: mod.slug.replace('./', ''), data: mod.contents }) + /** @param {ComponentFile} mod */ mod => ({ + path: mod.slug.replace('./', ''), + data: mod.contents + }) ) ); // files.push(...data.componentModules.map(mod => ({ path: `src/${mod.title.replace('../', '')}`, data: mod.contents }))); diff --git a/src/routes/_site-components/GuideContents.svelte b/src/routes/_site-components/GuideContents.svelte index ea61dd8ce..ed6b92972 100644 --- a/src/routes/_site-components/GuideContents.svelte +++ b/src/routes/_site-components/GuideContents.svelte @@ -1,9 +1,13 @@