diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index ddd4feec2be..e00f7ba81a8 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -29,6 +29,8 @@ jobs: run: npm run lint - name: Test run: npm run test + - name: 🧩 Typecheck + run: npm run typecheck - name: 🔤 Spell Check run: npm run spellcheck - uses: ./.github/workflows/actions/check-translations diff --git a/package-lock.json b/package-lock.json index 9b9071b3251..29521b62968 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,6 +35,7 @@ "devDependencies": { "@docusaurus/module-type-aliases": "^3.10.2", "@docusaurus/tsconfig": "^3.10.2", + "@docusaurus/types": "^3.10.2", "@ionic/prettier-config": "^4.0.0", "@types/react": "^19.2.18", "cspell": "^10.0.1", diff --git a/package.json b/package.json index 59eb086adef..71538c014d3 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "prettier": "prettier \"./**/*.{html,ts,tsx,js,jsx,md,mdx}\" --cache", "start": "docusaurus start", "test": "vitest run", + "typecheck": "tsc --noEmit", "swizzle": "docusaurus swizzle", "spellcheck": "cspell --no-progress --gitignore \"**/*.{md,mdx}\"" }, @@ -65,6 +66,7 @@ "devDependencies": { "@docusaurus/module-type-aliases": "^3.10.2", "@docusaurus/tsconfig": "^3.10.2", + "@docusaurus/types": "^3.10.2", "@ionic/prettier-config": "^4.0.0", "@types/react": "^19.2.18", "cspell": "^10.0.1", diff --git a/src/components/page/reference/ReleaseNotes/release-notes.d.json.ts b/src/components/page/reference/ReleaseNotes/release-notes.d.json.ts new file mode 100644 index 00000000000..925a836aba0 --- /dev/null +++ b/src/components/page/reference/ReleaseNotes/release-notes.d.json.ts @@ -0,0 +1,20 @@ +/** + * Types the sibling `release-notes.json`, which `scripts/release-notes.mjs` writes + * during `generate-markdown`. That file is gitignored and does not exist until a + * build runs, so this declaration keeps type checking independent of the GitHub API + * fetch that produces it. + * + * The `.d.json.ts` name is how TypeScript types a non-JS import; it requires + * `allowArbitraryExtensions`. + */ + +declare const releases: { + body: string; + name: string; + published_at: string; + tag_name: string; + type: string; + version: string; +}[]; + +export default releases; diff --git a/src/components/page/theming/SteppedColorGenerator/index.tsx b/src/components/page/theming/SteppedColorGenerator/index.tsx index 226b6dfe51c..4d76ffb9936 100755 --- a/src/components/page/theming/SteppedColorGenerator/index.tsx +++ b/src/components/page/theming/SteppedColorGenerator/index.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import { Component, Element, Listen, State, h } from '@stencil/core'; import { useEffect, useState } from 'react'; import CodeColor from '../CodeColor'; diff --git a/src/declarations.d.ts b/src/declarations.d.ts index 60f4e2ce67f..05607352113 100644 --- a/src/declarations.d.ts +++ b/src/declarations.d.ts @@ -1,12 +1,15 @@ /** * Type declarations for custom elements used in JSX. * - * These are web components registered at runtime (not React components), so - * TypeScript has no types for them and would otherwise reject the JSX usage. + * These are not React components, so TypeScript has no types for them and would + * otherwise reject the JSX usage: * * - `device-preview`: defined in src/components/global/Playground/device-preview.js * and registered via `defineCustomElement()`. Used by the Playground to render * examples inside an iOS/MD device frame. + * - `ion-icon`: registered by Ionic Framework, which the docs site loads globally. + * - `docs-card` / `docs-cards`: no JavaScript definition anywhere in this repo. They + * are unregistered tags used purely as styling hooks by DocsCard and DocsCards. */ // The import makes this file a module, so the block below augments React's // existing types instead of replacing them. @@ -16,6 +19,9 @@ declare module 'react' { namespace JSX { interface IntrinsicElements { 'device-preview': any; + 'ion-icon': any; + 'docs-card': any; + 'docs-cards': any; } } } diff --git a/src/theme/DocItem/Layout/frontMatter.interface.ts b/src/theme/DocItem/Layout/frontMatter.interface.ts new file mode 100644 index 00000000000..babfb0118e7 --- /dev/null +++ b/src/theme/DocItem/Layout/frontMatter.interface.ts @@ -0,0 +1,23 @@ +/** + * Front matter fields this site adds on top of the ones Docusaurus defines. They are + * set in the front matter of pages under `docs/` and `versioned_docs/`, and read by + * the sibling `index.tsx`. + * + * This file is ours. Only `index.tsx` in this folder is a copy of upstream. + * + * Docusaurus exports `DocFrontMatter` as a type alias rather than an interface, so + * declaration merging cannot add to it in place. Intersecting with it here keeps the + * upstream fields, so reads of both these and Docusaurus's own stay checked. + */ + +import type { DocFrontMatter } from '@docusaurus/plugin-content-docs'; + +export type DocsFrontMatter = DocFrontMatter & { + /** + * Renders a phone demo beside the page content. Setting it also suppresses the + * table of contents, since the two compete for the same column. + */ + demoUrl?: string; + /** Source link shown alongside the phone demo. */ + demoSourceUrl?: string; +}; diff --git a/src/theme/DocItem/Layout/index.tsx b/src/theme/DocItem/Layout/index.tsx index 4e64afe30e9..890afb5446b 100644 --- a/src/theme/DocItem/Layout/index.tsx +++ b/src/theme/DocItem/Layout/index.tsx @@ -26,6 +26,7 @@ import styles from '@docusaurus/theme-classic/lib/theme/DocItem/Layout/styles.mo // CUSTOM CODE import DocDemo from '@components/global/DocDemo'; +import type {DocsFrontMatter} from './frontMatter.interface'; // CUSTOM CODE END /** @@ -37,7 +38,7 @@ function useDocTOC() { const hidden = frontMatter.hide_table_of_contents; // CUSTOM CODE - const demoUrl = frontMatter.demoUrl; + const demoUrl = (frontMatter as DocsFrontMatter).demoUrl; const canRender = !hidden && toc.length > 0 && !demoUrl; // CUSTOM CODE END @@ -57,8 +58,7 @@ function useDocTOC() { // CUSTOM CODE function useDocDemo() { const {frontMatter} = useDoc(); - const demoUrl = frontMatter.demoUrl; - const demoSourceUrl = frontMatter.demoSourceUrl; + const {demoUrl, demoSourceUrl} = frontMatter as DocsFrontMatter; return { demoUrl, demoSourceUrl, diff --git a/tsconfig.json b/tsconfig.json index a1617086e76..0762b92ab84 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,4 +1,37 @@ { "extends": "@docusaurus/tsconfig", - "exclude": ["static/code/stackblitz/"], + "compilerOptions": { + /* + * `@docusaurus/tsconfig` sets `baseUrl` alongside a `@site/*` path mapping, but + * `extends` resolves `baseUrl` relative to the file that declares it, so the + * mapping points inside `node_modules/@docusaurus/tsconfig`. Redeclaring it here + * anchors it to the project root. + * + * TypeScript 6 deprecates `baseUrl` and 7 removes it. Dropping it here will not + * unblock those versions on its own, because the inherited one from + * `@docusaurus/tsconfig` triggers the same error, so this goes when upstream + * drops it. A `paths` mapping is not a substitute: the inherited `baseUrl` + * still wins. + */ + "baseUrl": ".", + /* + * Real ambient declarations for `@theme/*` and `@docusaurus/*`. Without them the + * catch-all `declare module` fallbacks in `index.d.ts` win and every swizzled + * component is typed as `any`. + */ + "types": ["@docusaurus/module-type-aliases", "@docusaurus/theme-classic"], + /* + * Lets `release-notes.d.json.ts` describe the generated `release-notes.json`. + * Ambient `declare module` cannot do this, as it does not apply to relative + * imports. + */ + "allowArbitraryExtensions": true + }, + /* + * `src` holds every TypeScript file in the project, and `index.d.ts` declares the + * `*.module.scss` imports those files rely on. Listing them explicitly also restores + * TypeScript's default excludes, notably `node_modules`, which the previous + * `exclude`-only config silently overrode. + */ + "include": ["src", "index.d.ts"] }