fix: vite
This commit is contained in:
7
node_modules/svelte/LICENSE.md
generated
vendored
Normal file
7
node_modules/svelte/LICENSE.md
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
Copyright (c) 2016-2025 [these people](https://github.com/sveltejs/svelte/graphs/contributors)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
41
node_modules/svelte/README.md
generated
vendored
Normal file
41
node_modules/svelte/README.md
generated
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
<a href="https://svelte.dev">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="../../assets/banner_dark.png">
|
||||
<img src="../../assets/banner.png" alt="Svelte - web development for the rest of us" />
|
||||
</picture>
|
||||
</a>
|
||||
|
||||
[](https://www.npmjs.com/package/svelte) [](LICENSE.md) [](https://svelte.dev/chat)
|
||||
|
||||
## What is Svelte?
|
||||
|
||||
Svelte is a new way to build web applications. It's a compiler that takes your declarative components and converts them into efficient JavaScript that surgically updates the DOM.
|
||||
|
||||
Learn more at the [Svelte website](https://svelte.dev), or stop by the [Discord chatroom](https://svelte.dev/chat).
|
||||
|
||||
## Getting started
|
||||
|
||||
You can play around with Svelte in the [tutorial](https://svelte.dev/tutorial), [examples](https://svelte.dev/examples), and [REPL](https://svelte.dev/repl).
|
||||
|
||||
When you're ready to build a full-fledge application, we recommend using [SvelteKit](https://svelte.dev/docs/kit):
|
||||
|
||||
```bash
|
||||
npx sv create my-app
|
||||
cd my-app
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
See [the SvelteKit documentation](https://svelte.dev/docs/kit) to learn more.
|
||||
|
||||
## Changelog
|
||||
|
||||
[The Changelog for this package is available on GitHub](https://github.com/sveltejs/svelte/blob/master/packages/svelte/CHANGELOG.md).
|
||||
|
||||
## Supporting Svelte
|
||||
|
||||
Svelte is an MIT-licensed open source project with its ongoing development made possible entirely by fantastic volunteers. If you'd like to support their efforts, please consider:
|
||||
|
||||
- [Becoming a backer on Open Collective](https://opencollective.com/svelte).
|
||||
|
||||
Funds donated via Open Collective will be used for compensating expenses related to Svelte's development.
|
||||
1
node_modules/svelte/action.d.ts
generated
vendored
Normal file
1
node_modules/svelte/action.d.ts
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import './types/index.js';
|
||||
1
node_modules/svelte/animate.d.ts
generated
vendored
Normal file
1
node_modules/svelte/animate.d.ts
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import './types/index.js';
|
||||
1
node_modules/svelte/compiler.d.ts
generated
vendored
Normal file
1
node_modules/svelte/compiler.d.ts
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import './types/index.js';
|
||||
1
node_modules/svelte/compiler/index.js
generated
vendored
Normal file
1
node_modules/svelte/compiler/index.js
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
3
node_modules/svelte/compiler/package.json
generated
vendored
Normal file
3
node_modules/svelte/compiler/package.json
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"type": "commonjs"
|
||||
}
|
||||
1
node_modules/svelte/easing.d.ts
generated
vendored
Normal file
1
node_modules/svelte/easing.d.ts
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import './types/index.js';
|
||||
2064
node_modules/svelte/elements.d.ts
generated
vendored
Normal file
2064
node_modules/svelte/elements.d.ts
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
node_modules/svelte/index.d.ts
generated
vendored
Normal file
1
node_modules/svelte/index.d.ts
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import './types/index.js';
|
||||
1
node_modules/svelte/legacy.d.ts
generated
vendored
Normal file
1
node_modules/svelte/legacy.d.ts
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import './types/index.js';
|
||||
1
node_modules/svelte/motion.d.ts
generated
vendored
Normal file
1
node_modules/svelte/motion.d.ts
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import './types/index.js';
|
||||
163
node_modules/svelte/package.json
generated
vendored
Normal file
163
node_modules/svelte/package.json
generated
vendored
Normal file
@@ -0,0 +1,163 @@
|
||||
{
|
||||
"name": "svelte",
|
||||
"description": "Cybernetically enhanced web apps",
|
||||
"license": "MIT",
|
||||
"version": "5.19.6",
|
||||
"type": "module",
|
||||
"types": "./types/index.d.ts",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"files": [
|
||||
"src",
|
||||
"!src/**/*.test.*",
|
||||
"types",
|
||||
"compiler",
|
||||
"*.d.ts",
|
||||
"README.md"
|
||||
],
|
||||
"module": "src/index-client.js",
|
||||
"main": "src/index-client.js",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./types/index.d.ts",
|
||||
"worker": "./src/index-server.js",
|
||||
"browser": "./src/index-client.js",
|
||||
"default": "./src/index-server.js"
|
||||
},
|
||||
"./package.json": "./package.json",
|
||||
"./action": {
|
||||
"types": "./types/index.d.ts"
|
||||
},
|
||||
"./animate": {
|
||||
"types": "./types/index.d.ts",
|
||||
"default": "./src/animate/index.js"
|
||||
},
|
||||
"./compiler": {
|
||||
"types": "./types/index.d.ts",
|
||||
"require": "./compiler/index.js",
|
||||
"default": "./src/compiler/index.js"
|
||||
},
|
||||
"./easing": {
|
||||
"types": "./types/index.d.ts",
|
||||
"default": "./src/easing/index.js"
|
||||
},
|
||||
"./elements": {
|
||||
"types": "./elements.d.ts"
|
||||
},
|
||||
"./internal": {
|
||||
"default": "./src/internal/index.js"
|
||||
},
|
||||
"./internal/client": {
|
||||
"default": "./src/internal/client/index.js"
|
||||
},
|
||||
"./internal/disclose-version": {
|
||||
"default": "./src/internal/disclose-version.js"
|
||||
},
|
||||
"./internal/flags/legacy": {
|
||||
"default": "./src/internal/flags/legacy.js"
|
||||
},
|
||||
"./internal/flags/tracing": {
|
||||
"default": "./src/internal/flags/tracing.js"
|
||||
},
|
||||
"./internal/server": {
|
||||
"default": "./src/internal/server/index.js"
|
||||
},
|
||||
"./legacy": {
|
||||
"types": "./types/index.d.ts",
|
||||
"worker": "./src/legacy/legacy-server.js",
|
||||
"browser": "./src/legacy/legacy-client.js",
|
||||
"default": "./src/legacy/legacy-server.js"
|
||||
},
|
||||
"./motion": {
|
||||
"types": "./types/index.d.ts",
|
||||
"default": "./src/motion/index.js"
|
||||
},
|
||||
"./reactivity": {
|
||||
"types": "./types/index.d.ts",
|
||||
"worker": "./src/reactivity/index-server.js",
|
||||
"browser": "./src/reactivity/index-client.js",
|
||||
"default": "./src/reactivity/index-server.js"
|
||||
},
|
||||
"./reactivity/window": {
|
||||
"types": "./types/index.d.ts",
|
||||
"default": "./src/reactivity/window/index.js"
|
||||
},
|
||||
"./server": {
|
||||
"types": "./types/index.d.ts",
|
||||
"default": "./src/server/index.js"
|
||||
},
|
||||
"./store": {
|
||||
"types": "./types/index.d.ts",
|
||||
"worker": "./src/store/index-server.js",
|
||||
"browser": "./src/store/index-client.js",
|
||||
"default": "./src/store/index-server.js"
|
||||
},
|
||||
"./transition": {
|
||||
"types": "./types/index.d.ts",
|
||||
"default": "./src/transition/index.js"
|
||||
},
|
||||
"./events": {
|
||||
"types": "./types/index.d.ts",
|
||||
"default": "./src/events/index.js"
|
||||
}
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/sveltejs/svelte.git",
|
||||
"directory": "packages/svelte"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/sveltejs/svelte/issues"
|
||||
},
|
||||
"homepage": "https://svelte.dev",
|
||||
"keywords": [
|
||||
"svelte",
|
||||
"UI",
|
||||
"framework",
|
||||
"templates",
|
||||
"templating"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@jridgewell/trace-mapping": "^0.3.25",
|
||||
"@playwright/test": "^1.46.1",
|
||||
"@rollup/plugin-commonjs": "^28.0.1",
|
||||
"@rollup/plugin-node-resolve": "^15.3.0",
|
||||
"@rollup/plugin-terser": "^0.4.4",
|
||||
"@rollup/plugin-virtual": "^3.0.2",
|
||||
"@types/aria-query": "^5.0.4",
|
||||
"@types/node": "^20.11.5",
|
||||
"dts-buddy": "^0.5.3",
|
||||
"esbuild": "^0.21.5",
|
||||
"rollup": "^4.22.4",
|
||||
"source-map": "^0.7.4",
|
||||
"tiny-glob": "^0.2.9",
|
||||
"typescript": "^5.5.4",
|
||||
"vitest": "^2.0.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ampproject/remapping": "^2.3.0",
|
||||
"@jridgewell/sourcemap-codec": "^1.5.0",
|
||||
"@types/estree": "^1.0.5",
|
||||
"acorn": "^8.12.1",
|
||||
"acorn-typescript": "^1.4.13",
|
||||
"aria-query": "^5.3.1",
|
||||
"axobject-query": "^4.1.0",
|
||||
"clsx": "^2.1.1",
|
||||
"esm-env": "^1.2.1",
|
||||
"esrap": "^1.4.3",
|
||||
"is-reference": "^3.0.3",
|
||||
"locate-character": "^3.0.0",
|
||||
"magic-string": "^0.30.11",
|
||||
"zimmerframe": "^1.1.2"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "node scripts/process-messages && rollup -c && pnpm generate:types && node scripts/check-treeshakeability.js",
|
||||
"dev": "node scripts/process-messages && rollup -cw",
|
||||
"check": "tsc --project tsconfig.runtime.json && tsc && cd ./tests/types && tsc",
|
||||
"check:watch": "tsc --watch",
|
||||
"generate:version": "node ./scripts/generate-version.js",
|
||||
"generate:types": "node ./scripts/generate-types.js && tsc -p tsconfig.generated.json",
|
||||
"knip": "pnpm dlx knip"
|
||||
}
|
||||
}
|
||||
69
node_modules/svelte/src/action/public.d.ts
generated
vendored
Normal file
69
node_modules/svelte/src/action/public.d.ts
generated
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* Actions can return an object containing the two properties defined in this interface. Both are optional.
|
||||
* - update: An action can have a parameter. This method will be called whenever that parameter changes,
|
||||
* immediately after Svelte has applied updates to the markup. `ActionReturn` and `ActionReturn<undefined>` both
|
||||
* mean that the action accepts no parameters.
|
||||
* - destroy: Method that is called after the element is unmounted
|
||||
*
|
||||
* Additionally, you can specify which additional attributes and events the action enables on the applied element.
|
||||
* This applies to TypeScript typings only and has no effect at runtime.
|
||||
*
|
||||
* Example usage:
|
||||
* ```ts
|
||||
* interface Attributes {
|
||||
* newprop?: string;
|
||||
* 'on:event': (e: CustomEvent<boolean>) => void;
|
||||
* }
|
||||
*
|
||||
* export function myAction(node: HTMLElement, parameter: Parameter): ActionReturn<Parameter, Attributes> {
|
||||
* // ...
|
||||
* return {
|
||||
* update: (updatedParameter) => {...},
|
||||
* destroy: () => {...}
|
||||
* };
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export interface ActionReturn<
|
||||
Parameter = undefined,
|
||||
Attributes extends Record<string, any> = Record<never, any>
|
||||
> {
|
||||
update?: (parameter: Parameter) => void;
|
||||
destroy?: () => void;
|
||||
/**
|
||||
* ### DO NOT USE THIS
|
||||
* This exists solely for type-checking and has no effect at runtime.
|
||||
* Set this through the `Attributes` generic instead.
|
||||
*/
|
||||
$$_attributes?: Attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Actions are functions that are called when an element is created.
|
||||
* You can use this interface to type such actions.
|
||||
* The following example defines an action that only works on `<div>` elements
|
||||
* and optionally accepts a parameter which it has a default value for:
|
||||
* ```ts
|
||||
* export const myAction: Action<HTMLDivElement, { someProperty: boolean } | undefined> = (node, param = { someProperty: true }) => {
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
* `Action<HTMLDivElement>` and `Action<HTMLDivElement, undefined>` both signal that the action accepts no parameters.
|
||||
*
|
||||
* You can return an object with methods `update` and `destroy` from the function and type which additional attributes and events it has.
|
||||
* See interface `ActionReturn` for more details.
|
||||
*/
|
||||
export interface Action<
|
||||
Element = HTMLElement,
|
||||
Parameter = undefined,
|
||||
Attributes extends Record<string, any> = Record<never, any>
|
||||
> {
|
||||
<Node extends Element>(
|
||||
...args: undefined extends Parameter
|
||||
? [node: Node, parameter?: Parameter]
|
||||
: [node: Node, parameter: Parameter]
|
||||
): void | ActionReturn<Parameter, Attributes>;
|
||||
}
|
||||
|
||||
// Implementation notes:
|
||||
// - undefined extends X instead of X extends undefined makes this work better with both strict and nonstrict mode
|
||||
502
node_modules/svelte/src/ambient.d.ts
generated
vendored
Normal file
502
node_modules/svelte/src/ambient.d.ts
generated
vendored
Normal file
@@ -0,0 +1,502 @@
|
||||
declare module '*.svelte' {
|
||||
// use prettier-ignore for a while because of https://github.com/sveltejs/language-tools/commit/026111228b5814a9109cc4d779d37fb02955fb8b
|
||||
// prettier-ignore
|
||||
import { SvelteComponent } from 'svelte'
|
||||
import { LegacyComponentType } from 'svelte/legacy';
|
||||
const Comp: LegacyComponentType;
|
||||
type Comp = SvelteComponent;
|
||||
export default Comp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Declares reactive state.
|
||||
*
|
||||
* Example:
|
||||
* ```ts
|
||||
* let count = $state(0);
|
||||
* ```
|
||||
*
|
||||
* https://svelte.dev/docs/svelte/$state
|
||||
*
|
||||
* @param initial The initial value
|
||||
*/
|
||||
declare function $state<T>(initial: T): T;
|
||||
declare function $state<T>(): T | undefined;
|
||||
|
||||
declare namespace $state {
|
||||
type Primitive = string | number | boolean | null | undefined;
|
||||
|
||||
type TypedArray =
|
||||
| Int8Array
|
||||
| Uint8Array
|
||||
| Uint8ClampedArray
|
||||
| Int16Array
|
||||
| Uint16Array
|
||||
| Int32Array
|
||||
| Uint32Array
|
||||
| Float32Array
|
||||
| Float64Array
|
||||
| BigInt64Array
|
||||
| BigUint64Array;
|
||||
|
||||
/** The things that `structuredClone` can handle — https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm */
|
||||
export type Cloneable =
|
||||
| ArrayBuffer
|
||||
| DataView
|
||||
| Date
|
||||
| Error
|
||||
| Map<any, any>
|
||||
| RegExp
|
||||
| Set<any>
|
||||
| TypedArray
|
||||
// web APIs
|
||||
| Blob
|
||||
| CryptoKey
|
||||
| DOMException
|
||||
| DOMMatrix
|
||||
| DOMMatrixReadOnly
|
||||
| DOMPoint
|
||||
| DOMPointReadOnly
|
||||
| DOMQuad
|
||||
| DOMRect
|
||||
| DOMRectReadOnly
|
||||
| File
|
||||
| FileList
|
||||
| FileSystemDirectoryHandle
|
||||
| FileSystemFileHandle
|
||||
| FileSystemHandle
|
||||
| ImageBitmap
|
||||
| ImageData
|
||||
| RTCCertificate
|
||||
| VideoFrame;
|
||||
|
||||
/** Turn `SvelteDate`, `SvelteMap` and `SvelteSet` into their non-reactive counterparts. (`URL` is uncloneable.) */
|
||||
type NonReactive<T> = T extends Date
|
||||
? Date
|
||||
: T extends Map<infer K, infer V>
|
||||
? Map<K, V>
|
||||
: T extends Set<infer K>
|
||||
? Set<K>
|
||||
: T;
|
||||
|
||||
type Snapshot<T> = T extends Primitive
|
||||
? T
|
||||
: T extends Cloneable
|
||||
? NonReactive<T>
|
||||
: T extends { toJSON(): infer R }
|
||||
? R
|
||||
: T extends Array<infer U>
|
||||
? Array<Snapshot<U>>
|
||||
: T extends object
|
||||
? T extends { [key: string]: any }
|
||||
? { [K in keyof T]: Snapshot<T[K]> }
|
||||
: never
|
||||
: never;
|
||||
|
||||
/**
|
||||
* Declares state that is _not_ made deeply reactive — instead of mutating it,
|
||||
* you must reassign it.
|
||||
*
|
||||
* Example:
|
||||
* ```ts
|
||||
* <script>
|
||||
* let items = $state.raw([0]);
|
||||
*
|
||||
* const addItem = () => {
|
||||
* items = [...items, items.length];
|
||||
* };
|
||||
* </script>
|
||||
*
|
||||
* <button on:click={addItem}>
|
||||
* {items.join(', ')}
|
||||
* </button>
|
||||
* ```
|
||||
*
|
||||
* https://svelte.dev/docs/svelte/$state#$state.raw
|
||||
*
|
||||
* @param initial The initial value
|
||||
*/
|
||||
export function raw<T>(initial: T): T;
|
||||
export function raw<T>(): T | undefined;
|
||||
/**
|
||||
* To take a static snapshot of a deeply reactive `$state` proxy, use `$state.snapshot`:
|
||||
*
|
||||
* Example:
|
||||
* ```ts
|
||||
* <script>
|
||||
* let counter = $state({ count: 0 });
|
||||
*
|
||||
* function onclick() {
|
||||
* // Will log `{ count: ... }` rather than `Proxy { ... }`
|
||||
* console.log($state.snapshot(counter));
|
||||
* };
|
||||
* </script>
|
||||
* ```
|
||||
*
|
||||
* https://svelte.dev/docs/svelte/$state#$state.snapshot
|
||||
*
|
||||
* @param state The value to snapshot
|
||||
*/
|
||||
export function snapshot<T>(state: T): Snapshot<T>;
|
||||
|
||||
// prevent intellisense from being unhelpful
|
||||
/** @deprecated */
|
||||
export const apply: never;
|
||||
/** @deprecated */
|
||||
// @ts-ignore
|
||||
export const arguments: never;
|
||||
/** @deprecated */
|
||||
export const bind: never;
|
||||
/** @deprecated */
|
||||
export const call: never;
|
||||
/** @deprecated */
|
||||
export const caller: never;
|
||||
/** @deprecated */
|
||||
export const length: never;
|
||||
/** @deprecated */
|
||||
export const name: never;
|
||||
/** @deprecated */
|
||||
export const prototype: never;
|
||||
/** @deprecated */
|
||||
export const toString: never;
|
||||
}
|
||||
|
||||
/**
|
||||
* Declares derived state, i.e. one that depends on other state variables.
|
||||
* The expression inside `$derived(...)` should be free of side-effects.
|
||||
*
|
||||
* Example:
|
||||
* ```ts
|
||||
* let double = $derived(count * 2);
|
||||
* ```
|
||||
*
|
||||
* https://svelte.dev/docs/svelte/$derived
|
||||
*
|
||||
* @param expression The derived state expression
|
||||
*/
|
||||
declare function $derived<T>(expression: T): T;
|
||||
|
||||
declare namespace $derived {
|
||||
/**
|
||||
* Sometimes you need to create complex derivations that don't fit inside a short expression.
|
||||
* In these cases, you can use `$derived.by` which accepts a function as its argument.
|
||||
*
|
||||
* Example:
|
||||
* ```ts
|
||||
* let total = $derived.by(() => {
|
||||
* let result = 0;
|
||||
* for (const n of numbers) {
|
||||
* result += n;
|
||||
* }
|
||||
* return result;
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* https://svelte.dev/docs/svelte/$derived#$derived.by
|
||||
*/
|
||||
export function by<T>(fn: () => T): T;
|
||||
|
||||
// prevent intellisense from being unhelpful
|
||||
/** @deprecated */
|
||||
export const apply: never;
|
||||
/** @deprecated */
|
||||
// @ts-ignore
|
||||
export const arguments: never;
|
||||
/** @deprecated */
|
||||
export const bind: never;
|
||||
/** @deprecated */
|
||||
export const call: never;
|
||||
/** @deprecated */
|
||||
export const caller: never;
|
||||
/** @deprecated */
|
||||
export const length: never;
|
||||
/** @deprecated */
|
||||
export const name: never;
|
||||
/** @deprecated */
|
||||
export const prototype: never;
|
||||
/** @deprecated */
|
||||
export const toString: never;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs code when a component is mounted to the DOM, and then whenever its dependencies change, i.e. `$state` or `$derived` values.
|
||||
* The timing of the execution is after the DOM has been updated.
|
||||
*
|
||||
* Example:
|
||||
* ```ts
|
||||
* $effect(() => console.log('The count is now ' + count));
|
||||
* ```
|
||||
*
|
||||
* If you return a function from the effect, it will be called right before the effect is run again, or when the component is unmounted.
|
||||
*
|
||||
* Does not run during server side rendering.
|
||||
*
|
||||
* https://svelte.dev/docs/svelte/$effect
|
||||
* @param fn The function to execute
|
||||
*/
|
||||
declare function $effect(fn: () => void | (() => void)): void;
|
||||
|
||||
declare namespace $effect {
|
||||
/**
|
||||
* Runs code right before a component is mounted to the DOM, and then whenever its dependencies change, i.e. `$state` or `$derived` values.
|
||||
* The timing of the execution is right before the DOM is updated.
|
||||
*
|
||||
* Example:
|
||||
* ```ts
|
||||
* $effect.pre(() => console.log('The count is now ' + count));
|
||||
* ```
|
||||
*
|
||||
* If you return a function from the effect, it will be called right before the effect is run again, or when the component is unmounted.
|
||||
*
|
||||
* Does not run during server side rendering.
|
||||
*
|
||||
* https://svelte.dev/docs/svelte/$effect#$effect.pre
|
||||
* @param fn The function to execute
|
||||
*/
|
||||
export function pre(fn: () => void | (() => void)): void;
|
||||
|
||||
/**
|
||||
* The `$effect.tracking` rune is an advanced feature that tells you whether or not the code is running inside a tracking context, such as an effect or inside your template.
|
||||
*
|
||||
* Example:
|
||||
* ```svelte
|
||||
* <script>
|
||||
* console.log('in component setup:', $effect.tracking()); // false
|
||||
*
|
||||
* $effect(() => {
|
||||
* console.log('in effect:', $effect.tracking()); // true
|
||||
* });
|
||||
* </script>
|
||||
*
|
||||
* <p>in template: {$effect.tracking()}</p> <!-- true -->
|
||||
* ```
|
||||
*
|
||||
* This allows you to (for example) add things like subscriptions without causing memory leaks, by putting them in child effects.
|
||||
*
|
||||
* https://svelte.dev/docs/svelte/$effect#$effect.tracking
|
||||
*/
|
||||
export function tracking(): boolean;
|
||||
|
||||
/**
|
||||
* The `$effect.root` rune is an advanced feature that creates a non-tracked scope that doesn't auto-cleanup. This is useful for
|
||||
* nested effects that you want to manually control. This rune also allows for creation of effects outside of the component
|
||||
* initialisation phase.
|
||||
*
|
||||
* Example:
|
||||
* ```svelte
|
||||
* <script>
|
||||
* let count = $state(0);
|
||||
*
|
||||
* const cleanup = $effect.root(() => {
|
||||
* $effect(() => {
|
||||
* console.log(count);
|
||||
* })
|
||||
*
|
||||
* return () => {
|
||||
* console.log('effect root cleanup');
|
||||
* }
|
||||
* });
|
||||
* </script>
|
||||
*
|
||||
* <button onclick={() => cleanup()}>cleanup</button>
|
||||
* ```
|
||||
*
|
||||
* https://svelte.dev/docs/svelte/$effect#$effect.root
|
||||
*/
|
||||
export function root(fn: () => void | (() => void)): () => void;
|
||||
|
||||
// prevent intellisense from being unhelpful
|
||||
/** @deprecated */
|
||||
export const apply: never;
|
||||
/** @deprecated */
|
||||
// @ts-ignore
|
||||
export const arguments: never;
|
||||
/** @deprecated */
|
||||
export const bind: never;
|
||||
/** @deprecated */
|
||||
export const call: never;
|
||||
/** @deprecated */
|
||||
export const caller: never;
|
||||
/** @deprecated */
|
||||
export const length: never;
|
||||
/** @deprecated */
|
||||
export const name: never;
|
||||
/** @deprecated */
|
||||
export const prototype: never;
|
||||
/** @deprecated */
|
||||
export const toString: never;
|
||||
}
|
||||
|
||||
/**
|
||||
* Declares the props that a component accepts. Example:
|
||||
*
|
||||
* ```ts
|
||||
* let { optionalProp = 42, requiredProp, bindableProp = $bindable() }: { optionalProp?: number; requiredProps: string; bindableProp: boolean } = $props();
|
||||
* ```
|
||||
*
|
||||
* https://svelte.dev/docs/svelte/$props
|
||||
*/
|
||||
declare function $props(): any;
|
||||
|
||||
declare namespace $props {
|
||||
// prevent intellisense from being unhelpful
|
||||
/** @deprecated */
|
||||
export const apply: never;
|
||||
/** @deprecated */
|
||||
// @ts-ignore
|
||||
export const arguments: never;
|
||||
/** @deprecated */
|
||||
export const bind: never;
|
||||
/** @deprecated */
|
||||
export const call: never;
|
||||
/** @deprecated */
|
||||
export const caller: never;
|
||||
/** @deprecated */
|
||||
export const length: never;
|
||||
/** @deprecated */
|
||||
export const name: never;
|
||||
/** @deprecated */
|
||||
export const prototype: never;
|
||||
/** @deprecated */
|
||||
export const toString: never;
|
||||
}
|
||||
|
||||
/**
|
||||
* Declares a prop as bindable, meaning the parent component can use `bind:propName={value}` to bind to it.
|
||||
*
|
||||
* ```ts
|
||||
* let { propName = $bindable() }: { propName: boolean } = $props();
|
||||
* ```
|
||||
*
|
||||
* https://svelte.dev/docs/svelte/$bindable
|
||||
*/
|
||||
declare function $bindable<T>(fallback?: T): T;
|
||||
|
||||
declare namespace $bindable {
|
||||
// prevent intellisense from being unhelpful
|
||||
/** @deprecated */
|
||||
export const apply: never;
|
||||
/** @deprecated */
|
||||
// @ts-ignore
|
||||
export const arguments: never;
|
||||
/** @deprecated */
|
||||
export const bind: never;
|
||||
/** @deprecated */
|
||||
export const call: never;
|
||||
/** @deprecated */
|
||||
export const caller: never;
|
||||
/** @deprecated */
|
||||
export const length: never;
|
||||
/** @deprecated */
|
||||
export const name: never;
|
||||
/** @deprecated */
|
||||
export const prototype: never;
|
||||
/** @deprecated */
|
||||
export const toString: never;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inspects one or more values whenever they, or the properties they contain, change. Example:
|
||||
*
|
||||
* ```ts
|
||||
* $inspect(someValue, someOtherValue)
|
||||
* ```
|
||||
*
|
||||
* `$inspect` returns a `with` function, which you can invoke with a callback function that
|
||||
* will be called with the value and the event type (`'init'` or `'update'`) on every change.
|
||||
* By default, the values will be logged to the console.
|
||||
*
|
||||
* ```ts
|
||||
* $inspect(x).with(console.trace);
|
||||
* $inspect(x, y).with(() => { debugger; });
|
||||
* ```
|
||||
*
|
||||
* https://svelte.dev/docs/svelte/$inspect
|
||||
*/
|
||||
declare function $inspect<T extends any[]>(
|
||||
...values: T
|
||||
): { with: (fn: (type: 'init' | 'update', ...values: T) => void) => void };
|
||||
|
||||
declare namespace $inspect {
|
||||
/**
|
||||
* Tracks which reactive state changes caused an effect to re-run. Must be the first
|
||||
* statement of a function body. Example:
|
||||
*
|
||||
* ```svelte
|
||||
* <script>
|
||||
* let count = $state(0);
|
||||
*
|
||||
* $effect(() => {
|
||||
* $inspect.trace('my effect');
|
||||
*
|
||||
* count;
|
||||
* });
|
||||
* </script>
|
||||
*/
|
||||
export function trace(name?: string): void;
|
||||
|
||||
// prevent intellisense from being unhelpful
|
||||
/** @deprecated */
|
||||
export const apply: never;
|
||||
/** @deprecated */
|
||||
// @ts-ignore
|
||||
export const arguments: never;
|
||||
/** @deprecated */
|
||||
export const bind: never;
|
||||
/** @deprecated */
|
||||
export const call: never;
|
||||
/** @deprecated */
|
||||
export const caller: never;
|
||||
/** @deprecated */
|
||||
export const length: never;
|
||||
/** @deprecated */
|
||||
export const name: never;
|
||||
/** @deprecated */
|
||||
export const prototype: never;
|
||||
/** @deprecated */
|
||||
export const toString: never;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the `this` reference of the custom element that contains this component. Example:
|
||||
*
|
||||
* ```svelte
|
||||
* <svelte:options customElement="my-element" />
|
||||
*
|
||||
* <script>
|
||||
* function greet(greeting) {
|
||||
* $host().dispatchEvent(new CustomEvent('greeting', { detail: greeting }))
|
||||
* }
|
||||
* </script>
|
||||
*
|
||||
* <button onclick={() => greet('hello')}>say hello</button>
|
||||
* ```
|
||||
*
|
||||
* Only available inside custom element components, and only on the client-side.
|
||||
*
|
||||
* https://svelte.dev/docs/svelte/$host
|
||||
*/
|
||||
declare function $host<El extends HTMLElement = HTMLElement>(): El;
|
||||
|
||||
declare namespace $host {
|
||||
// prevent intellisense from being unhelpful
|
||||
/** @deprecated */
|
||||
export const apply: never;
|
||||
/** @deprecated */
|
||||
// @ts-ignore
|
||||
export const arguments: never;
|
||||
/** @deprecated */
|
||||
export const bind: never;
|
||||
/** @deprecated */
|
||||
export const call: never;
|
||||
/** @deprecated */
|
||||
export const caller: never;
|
||||
/** @deprecated */
|
||||
export const length: never;
|
||||
/** @deprecated */
|
||||
export const name: never;
|
||||
/** @deprecated */
|
||||
export const prototype: never;
|
||||
/** @deprecated */
|
||||
export const toString: never;
|
||||
}
|
||||
78
node_modules/svelte/src/animate/index.js
generated
vendored
Normal file
78
node_modules/svelte/src/animate/index.js
generated
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
/** @import { FlipParams, AnimationConfig } from './public.js' */
|
||||
import { cubicOut } from '../easing/index.js';
|
||||
|
||||
/**
|
||||
* The flip function calculates the start and end position of an element and animates between them, translating the x and y values.
|
||||
* `flip` stands for [First, Last, Invert, Play](https://aerotwist.com/blog/flip-your-animations/).
|
||||
*
|
||||
* @param {Element} node
|
||||
* @param {{ from: DOMRect; to: DOMRect }} fromTo
|
||||
* @param {FlipParams} params
|
||||
* @returns {AnimationConfig}
|
||||
*/
|
||||
export function flip(node, { from, to }, params = {}) {
|
||||
var { delay = 0, duration = (d) => Math.sqrt(d) * 120, easing = cubicOut } = params;
|
||||
|
||||
var style = getComputedStyle(node);
|
||||
|
||||
// find the transform origin, expressed as a pair of values between 0 and 1
|
||||
var transform = style.transform === 'none' ? '' : style.transform;
|
||||
var [ox, oy] = style.transformOrigin.split(' ').map(parseFloat);
|
||||
ox /= node.clientWidth;
|
||||
oy /= node.clientHeight;
|
||||
|
||||
// calculate effect of parent transforms and zoom
|
||||
var zoom = get_zoom(node); // https://drafts.csswg.org/css-viewport/#effective-zoom
|
||||
var sx = node.clientWidth / to.width / zoom;
|
||||
var sy = node.clientHeight / to.height / zoom;
|
||||
|
||||
// find the starting position of the transform origin
|
||||
var fx = from.left + from.width * ox;
|
||||
var fy = from.top + from.height * oy;
|
||||
|
||||
// find the ending position of the transform origin
|
||||
var tx = to.left + to.width * ox;
|
||||
var ty = to.top + to.height * oy;
|
||||
|
||||
// find the translation at the start of the transform
|
||||
var dx = (fx - tx) * sx;
|
||||
var dy = (fy - ty) * sy;
|
||||
|
||||
// find the relative scale at the start of the transform
|
||||
var dsx = from.width / to.width;
|
||||
var dsy = from.height / to.height;
|
||||
|
||||
return {
|
||||
delay,
|
||||
duration: typeof duration === 'function' ? duration(Math.sqrt(dx * dx + dy * dy)) : duration,
|
||||
easing,
|
||||
css: (t, u) => {
|
||||
var x = u * dx;
|
||||
var y = u * dy;
|
||||
var sx = t + u * dsx;
|
||||
var sy = t + u * dsy;
|
||||
|
||||
return `transform: ${transform} translate(${x}px, ${y}px) scale(${sx}, ${sy});`;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Element} element
|
||||
*/
|
||||
function get_zoom(element) {
|
||||
if ('currentCSSZoom' in element) {
|
||||
return /** @type {number} */ (element.currentCSSZoom);
|
||||
}
|
||||
|
||||
/** @type {Element | null} */
|
||||
var current = element;
|
||||
var zoom = 1;
|
||||
|
||||
while (current !== null) {
|
||||
zoom *= +getComputedStyle(current).zoom;
|
||||
current = /** @type {Element | null} */ (current.parentElement);
|
||||
}
|
||||
|
||||
return zoom;
|
||||
}
|
||||
16
node_modules/svelte/src/animate/public.d.ts
generated
vendored
Normal file
16
node_modules/svelte/src/animate/public.d.ts
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
// todo: same as Transition, should it be shared?
|
||||
export interface AnimationConfig {
|
||||
delay?: number;
|
||||
duration?: number;
|
||||
easing?: (t: number) => number;
|
||||
css?: (t: number, u: number) => string;
|
||||
tick?: (t: number, u: number) => void;
|
||||
}
|
||||
|
||||
export interface FlipParams {
|
||||
delay?: number;
|
||||
duration?: number | ((len: number) => number);
|
||||
easing?: (t: number) => number;
|
||||
}
|
||||
|
||||
export * from './index.js';
|
||||
1593
node_modules/svelte/src/compiler/errors.js
generated
vendored
Normal file
1593
node_modules/svelte/src/compiler/errors.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
169
node_modules/svelte/src/compiler/index.js
generated
vendored
Normal file
169
node_modules/svelte/src/compiler/index.js
generated
vendored
Normal file
@@ -0,0 +1,169 @@
|
||||
/** @import { LegacyRoot } from './types/legacy-nodes.js' */
|
||||
/** @import { CompileOptions, CompileResult, ValidatedCompileOptions, ModuleCompileOptions } from '#compiler' */
|
||||
/** @import { AST } from './public.js' */
|
||||
import { walk as zimmerframe_walk } from 'zimmerframe';
|
||||
import { convert } from './legacy.js';
|
||||
import { parse as parse_acorn } from './phases/1-parse/acorn.js';
|
||||
import { parse as _parse } from './phases/1-parse/index.js';
|
||||
import { remove_typescript_nodes } from './phases/1-parse/remove_typescript_nodes.js';
|
||||
import { analyze_component, analyze_module } from './phases/2-analyze/index.js';
|
||||
import { transform_component, transform_module } from './phases/3-transform/index.js';
|
||||
import { validate_component_options, validate_module_options } from './validate-options.js';
|
||||
import * as state from './state.js';
|
||||
export { default as preprocess } from './preprocess/index.js';
|
||||
|
||||
/**
|
||||
* `compile` converts your `.svelte` source code into a JavaScript module that exports a component
|
||||
*
|
||||
* @param {string} source The component source code
|
||||
* @param {CompileOptions} options The compiler options
|
||||
* @returns {CompileResult}
|
||||
*/
|
||||
export function compile(source, options) {
|
||||
source = remove_bom(source);
|
||||
state.reset_warning_filter(options.warningFilter);
|
||||
const validated = validate_component_options(options, '');
|
||||
state.reset(source, validated);
|
||||
|
||||
let parsed = _parse(source);
|
||||
|
||||
const { customElement: customElementOptions, ...parsed_options } = parsed.options || {};
|
||||
|
||||
/** @type {ValidatedCompileOptions} */
|
||||
const combined_options = {
|
||||
...validated,
|
||||
...parsed_options,
|
||||
customElementOptions
|
||||
};
|
||||
|
||||
if (parsed.metadata.ts) {
|
||||
parsed = {
|
||||
...parsed,
|
||||
fragment: parsed.fragment && remove_typescript_nodes(parsed.fragment),
|
||||
instance: parsed.instance && remove_typescript_nodes(parsed.instance),
|
||||
module: parsed.module && remove_typescript_nodes(parsed.module)
|
||||
};
|
||||
}
|
||||
|
||||
const analysis = analyze_component(parsed, source, combined_options);
|
||||
const result = transform_component(analysis, source, combined_options);
|
||||
result.ast = to_public_ast(source, parsed, options.modernAst);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* `compileModule` takes your JavaScript source code containing runes, and turns it into a JavaScript module.
|
||||
*
|
||||
* @param {string} source The component source code
|
||||
* @param {ModuleCompileOptions} options
|
||||
* @returns {CompileResult}
|
||||
*/
|
||||
export function compileModule(source, options) {
|
||||
source = remove_bom(source);
|
||||
state.reset_warning_filter(options.warningFilter);
|
||||
const validated = validate_module_options(options, '');
|
||||
state.reset(source, validated);
|
||||
|
||||
const analysis = analyze_module(parse_acorn(source, false), validated);
|
||||
return transform_module(analysis, source, validated);
|
||||
}
|
||||
|
||||
/**
|
||||
* The parse function parses a component, returning only its abstract syntax tree.
|
||||
*
|
||||
* The `modern` option (`false` by default in Svelte 5) makes the parser return a modern AST instead of the legacy AST.
|
||||
* `modern` will become `true` by default in Svelte 6, and the option will be removed in Svelte 7.
|
||||
*
|
||||
* @overload
|
||||
* @param {string} source
|
||||
* @param {{ filename?: string; modern: true; loose?: boolean }} options
|
||||
* @returns {AST.Root}
|
||||
*/
|
||||
|
||||
/**
|
||||
* The parse function parses a component, returning only its abstract syntax tree.
|
||||
*
|
||||
* The `modern` option (`false` by default in Svelte 5) makes the parser return a modern AST instead of the legacy AST.
|
||||
* `modern` will become `true` by default in Svelte 6, and the option will be removed in Svelte 7.
|
||||
*
|
||||
* @overload
|
||||
* @param {string} source
|
||||
* @param {{ filename?: string; modern?: false; loose?: boolean }} [options]
|
||||
* @returns {Record<string, any>}
|
||||
*/
|
||||
|
||||
/**
|
||||
* The parse function parses a component, returning only its abstract syntax tree.
|
||||
*
|
||||
* The `modern` option (`false` by default in Svelte 5) makes the parser return a modern AST instead of the legacy AST.
|
||||
* `modern` will become `true` by default in Svelte 6, and the option will be removed in Svelte 7.
|
||||
*
|
||||
* The `loose` option, available since 5.13.0, tries to always return an AST even if the input will not successfully compile.
|
||||
*
|
||||
* @param {string} source
|
||||
* @param {{ filename?: string; rootDir?: string; modern?: boolean; loose?: boolean }} [options]
|
||||
* @returns {AST.Root | LegacyRoot}
|
||||
*/
|
||||
export function parse(source, { filename, rootDir, modern, loose } = {}) {
|
||||
source = remove_bom(source);
|
||||
state.reset_warning_filter(() => false);
|
||||
state.reset(source, { filename: filename ?? '(unknown)', rootDir });
|
||||
|
||||
const ast = _parse(source, loose);
|
||||
return to_public_ast(source, ast, modern);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} source
|
||||
* @param {AST.Root} ast
|
||||
* @param {boolean | undefined} modern
|
||||
*/
|
||||
function to_public_ast(source, ast, modern) {
|
||||
if (modern) {
|
||||
const clean = (/** @type {any} */ node) => {
|
||||
delete node.metadata;
|
||||
};
|
||||
|
||||
ast.options?.attributes.forEach((attribute) => {
|
||||
clean(attribute);
|
||||
clean(attribute.value);
|
||||
if (Array.isArray(attribute.value)) {
|
||||
attribute.value.forEach(clean);
|
||||
}
|
||||
});
|
||||
|
||||
// remove things that we don't want to treat as public API
|
||||
return zimmerframe_walk(ast, null, {
|
||||
_(node, { next }) {
|
||||
clean(node);
|
||||
next();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return convert(source, ast);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the byte order mark from a string if it's present since it would mess with our template generation logic
|
||||
* @param {string} source
|
||||
*/
|
||||
function remove_bom(source) {
|
||||
if (source.charCodeAt(0) === 0xfeff) {
|
||||
return source.slice(1);
|
||||
}
|
||||
return source;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Replace this with `import { walk } from 'estree-walker'`
|
||||
* @returns {never}
|
||||
*/
|
||||
export function walk() {
|
||||
throw new Error(
|
||||
`'svelte/compiler' no longer exports a \`walk\` utility — please import it directly from 'estree-walker' instead`
|
||||
);
|
||||
}
|
||||
|
||||
export { VERSION } from '../version.js';
|
||||
export { migrate } from './migrate/index.js';
|
||||
628
node_modules/svelte/src/compiler/legacy.js
generated
vendored
Normal file
628
node_modules/svelte/src/compiler/legacy.js
generated
vendored
Normal file
@@ -0,0 +1,628 @@
|
||||
/** @import { Expression } from 'estree' */
|
||||
/** @import { AST } from '#compiler' */
|
||||
/** @import * as Legacy from './types/legacy-nodes.js' */
|
||||
import { walk } from 'zimmerframe';
|
||||
import {
|
||||
regex_ends_with_whitespaces,
|
||||
regex_not_whitespace,
|
||||
regex_starts_with_whitespaces
|
||||
} from './phases/patterns.js';
|
||||
import { extract_svelte_ignore } from './utils/extract_svelte_ignore.js';
|
||||
|
||||
/**
|
||||
* Some of the legacy Svelte AST nodes remove whitespace from the start and end of their children.
|
||||
* @param {AST.TemplateNode[]} nodes
|
||||
*/
|
||||
function remove_surrounding_whitespace_nodes(nodes) {
|
||||
const first = nodes.at(0);
|
||||
const last = nodes.at(-1);
|
||||
|
||||
if (first?.type === 'Text') {
|
||||
if (!regex_not_whitespace.test(first.data)) {
|
||||
nodes.shift();
|
||||
} else {
|
||||
first.data = first.data.replace(regex_starts_with_whitespaces, '');
|
||||
}
|
||||
}
|
||||
if (last?.type === 'Text') {
|
||||
if (!regex_not_whitespace.test(last.data)) {
|
||||
nodes.pop();
|
||||
} else {
|
||||
last.data = last.data.replace(regex_ends_with_whitespaces, '');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform our nice modern AST into the monstrosity emitted by Svelte 4
|
||||
* @param {string} source
|
||||
* @param {AST.Root} ast
|
||||
* @returns {Legacy.LegacyRoot}
|
||||
*/
|
||||
export function convert(source, ast) {
|
||||
const root = /** @type {AST.SvelteNode | Legacy.LegacySvelteNode} */ (ast);
|
||||
|
||||
return /** @type {Legacy.LegacyRoot} */ (
|
||||
walk(root, null, {
|
||||
_(node, { next }) {
|
||||
// @ts-ignore
|
||||
delete node.metadata;
|
||||
next();
|
||||
},
|
||||
// @ts-ignore
|
||||
Root(node, { visit }) {
|
||||
const { instance, module, options } = node;
|
||||
|
||||
// Insert svelte:options back into the root nodes
|
||||
if (/** @type {any} */ (options)?.__raw__) {
|
||||
let idx = node.fragment.nodes.findIndex((node) => options.end <= node.start);
|
||||
if (idx === -1) {
|
||||
idx = node.fragment.nodes.length;
|
||||
}
|
||||
|
||||
node.fragment.nodes.splice(idx, 0, /** @type {any} */ (options).__raw__);
|
||||
}
|
||||
|
||||
/** @type {number | null} */
|
||||
let start = null;
|
||||
|
||||
/** @type {number | null} */
|
||||
let end = null;
|
||||
|
||||
if (node.fragment.nodes.length > 0) {
|
||||
const first = /** @type {AST.BaseNode} */ (node.fragment.nodes.at(0));
|
||||
const last = /** @type {AST.BaseNode} */ (node.fragment.nodes.at(-1));
|
||||
|
||||
start = first.start;
|
||||
end = last.end;
|
||||
|
||||
while (/\s/.test(source[start])) start += 1;
|
||||
while (/\s/.test(source[end - 1])) end -= 1;
|
||||
}
|
||||
|
||||
if (instance) {
|
||||
// @ts-ignore
|
||||
delete instance.attributes;
|
||||
}
|
||||
|
||||
if (module) {
|
||||
// @ts-ignore
|
||||
delete module.attributes;
|
||||
}
|
||||
|
||||
return {
|
||||
html: {
|
||||
type: 'Fragment',
|
||||
start,
|
||||
end,
|
||||
children: node.fragment.nodes.map((child) => visit(child))
|
||||
},
|
||||
instance,
|
||||
module,
|
||||
css: ast.css ? visit(ast.css) : undefined
|
||||
};
|
||||
},
|
||||
AnimateDirective(node) {
|
||||
return { ...node, type: 'Animation' };
|
||||
},
|
||||
// @ts-ignore
|
||||
AwaitBlock(node, { visit }) {
|
||||
let pendingblock = {
|
||||
type: 'PendingBlock',
|
||||
/** @type {number | null} */
|
||||
start: null,
|
||||
/** @type {number | null} */
|
||||
end: null,
|
||||
children: node.pending?.nodes.map((child) => visit(child)) ?? [],
|
||||
skip: true
|
||||
};
|
||||
|
||||
let thenblock = {
|
||||
type: 'ThenBlock',
|
||||
/** @type {number | null} */
|
||||
start: null,
|
||||
/** @type {number | null} */
|
||||
end: null,
|
||||
children: node.then?.nodes.map((child) => visit(child)) ?? [],
|
||||
skip: true
|
||||
};
|
||||
|
||||
let catchblock = {
|
||||
type: 'CatchBlock',
|
||||
/** @type {number | null} */
|
||||
start: null,
|
||||
/** @type {number | null} */
|
||||
end: null,
|
||||
children: node.catch?.nodes.map((child) => visit(child)) ?? [],
|
||||
skip: true
|
||||
};
|
||||
|
||||
if (node.pending) {
|
||||
const first = node.pending.nodes.at(0);
|
||||
const last = node.pending.nodes.at(-1);
|
||||
|
||||
pendingblock.start = first?.start ?? source.indexOf('}', node.expression.end) + 1;
|
||||
pendingblock.end = last?.end ?? pendingblock.start;
|
||||
pendingblock.skip = false;
|
||||
}
|
||||
|
||||
if (node.then) {
|
||||
const first = node.then.nodes.at(0);
|
||||
const last = node.then.nodes.at(-1);
|
||||
|
||||
thenblock.start =
|
||||
pendingblock.end ?? first?.start ?? source.indexOf('}', node.expression.end) + 1;
|
||||
thenblock.end =
|
||||
last?.end ?? source.lastIndexOf('}', pendingblock.end ?? node.expression.end) + 1;
|
||||
thenblock.skip = false;
|
||||
}
|
||||
|
||||
if (node.catch) {
|
||||
const first = node.catch.nodes.at(0);
|
||||
const last = node.catch.nodes.at(-1);
|
||||
|
||||
catchblock.start =
|
||||
thenblock.end ??
|
||||
pendingblock.end ??
|
||||
first?.start ??
|
||||
source.indexOf('}', node.expression.end) + 1;
|
||||
catchblock.end =
|
||||
last?.end ??
|
||||
source.lastIndexOf('}', thenblock.end ?? pendingblock.end ?? node.expression.end) + 1;
|
||||
catchblock.skip = false;
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'AwaitBlock',
|
||||
start: node.start,
|
||||
end: node.end,
|
||||
expression: node.expression,
|
||||
value: node.value,
|
||||
error: node.error,
|
||||
pending: pendingblock,
|
||||
then: thenblock,
|
||||
catch: catchblock
|
||||
};
|
||||
},
|
||||
BindDirective(node) {
|
||||
return { ...node, type: 'Binding' };
|
||||
},
|
||||
ClassDirective(node) {
|
||||
return { ...node, type: 'Class' };
|
||||
},
|
||||
Comment(node) {
|
||||
return {
|
||||
...node,
|
||||
ignores: extract_svelte_ignore(node.start, node.data, false)
|
||||
};
|
||||
},
|
||||
ComplexSelector(node, { next }) {
|
||||
next(); // delete inner metadata/parent properties
|
||||
|
||||
const children = [];
|
||||
|
||||
for (const child of node.children) {
|
||||
if (child.combinator) {
|
||||
children.push(child.combinator);
|
||||
}
|
||||
|
||||
children.push(...child.selectors);
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'Selector',
|
||||
start: node.start,
|
||||
end: node.end,
|
||||
children
|
||||
};
|
||||
},
|
||||
Component(node, { visit }) {
|
||||
return {
|
||||
type: 'InlineComponent',
|
||||
start: node.start,
|
||||
end: node.end,
|
||||
name: node.name,
|
||||
attributes: node.attributes.map(
|
||||
(child) => /** @type {Legacy.LegacyAttributeLike} */ (visit(child))
|
||||
),
|
||||
children: node.fragment.nodes.map(
|
||||
(child) => /** @type {Legacy.LegacyElementLike} */ (visit(child))
|
||||
)
|
||||
};
|
||||
},
|
||||
// @ts-ignore
|
||||
ConstTag(node) {
|
||||
if (/** @type {Legacy.LegacyConstTag} */ (node).expression !== undefined) {
|
||||
return node;
|
||||
}
|
||||
|
||||
const modern_node = /** @type {AST.ConstTag} */ (node);
|
||||
const { id: left } = { ...modern_node.declaration.declarations[0] };
|
||||
// @ts-ignore
|
||||
delete left.typeAnnotation;
|
||||
return {
|
||||
type: 'ConstTag',
|
||||
start: modern_node.start,
|
||||
end: node.end,
|
||||
expression: {
|
||||
type: 'AssignmentExpression',
|
||||
start: (modern_node.declaration.start ?? 0) + 'const '.length,
|
||||
end: modern_node.declaration.end ?? 0,
|
||||
operator: '=',
|
||||
left,
|
||||
right: modern_node.declaration.declarations[0].init
|
||||
}
|
||||
};
|
||||
},
|
||||
// @ts-ignore
|
||||
KeyBlock(node, { visit }) {
|
||||
remove_surrounding_whitespace_nodes(node.fragment.nodes);
|
||||
return {
|
||||
type: 'KeyBlock',
|
||||
start: node.start,
|
||||
end: node.end,
|
||||
expression: node.expression,
|
||||
children: node.fragment.nodes.map(
|
||||
(child) => /** @type {Legacy.LegacyElementLike} */ (visit(child))
|
||||
)
|
||||
};
|
||||
},
|
||||
// @ts-ignore
|
||||
EachBlock(node, { visit }) {
|
||||
let elseblock = undefined;
|
||||
|
||||
if (node.fallback) {
|
||||
const first = node.fallback.nodes.at(0);
|
||||
const end = source.lastIndexOf('{', /** @type {number} */ (node.end) - 1);
|
||||
const start = first?.start ?? end;
|
||||
|
||||
remove_surrounding_whitespace_nodes(node.fallback.nodes);
|
||||
|
||||
elseblock = {
|
||||
type: 'ElseBlock',
|
||||
start,
|
||||
end,
|
||||
children: node.fallback.nodes.map((child) => visit(child))
|
||||
};
|
||||
}
|
||||
|
||||
remove_surrounding_whitespace_nodes(node.body.nodes);
|
||||
|
||||
return {
|
||||
type: 'EachBlock',
|
||||
start: node.start,
|
||||
end: node.end,
|
||||
children: node.body.nodes.map((child) => visit(child)),
|
||||
context: node.context,
|
||||
expression: node.expression,
|
||||
index: node.index,
|
||||
key: node.key,
|
||||
else: elseblock
|
||||
};
|
||||
},
|
||||
ExpressionTag(node, { path }) {
|
||||
const parent = path.at(-1);
|
||||
if (parent?.type === 'Attribute') {
|
||||
if (source[parent.start] === '{') {
|
||||
return {
|
||||
type: 'AttributeShorthand',
|
||||
start: node.start,
|
||||
end: node.end,
|
||||
expression: node.expression
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'MustacheTag',
|
||||
start: node.start,
|
||||
end: node.end,
|
||||
expression: node.expression
|
||||
};
|
||||
},
|
||||
HtmlTag(node) {
|
||||
return { ...node, type: 'RawMustacheTag' };
|
||||
},
|
||||
// @ts-ignore
|
||||
IfBlock(node, { visit }) {
|
||||
let elseblock = undefined;
|
||||
if (node.alternate) {
|
||||
let nodes = node.alternate.nodes;
|
||||
if (nodes.length === 1 && nodes[0].type === 'IfBlock' && nodes[0].elseif) {
|
||||
nodes = nodes[0].consequent.nodes;
|
||||
}
|
||||
|
||||
const end = source.lastIndexOf('{', /** @type {number} */ (node.end) - 1);
|
||||
const start = nodes.at(0)?.start ?? end;
|
||||
|
||||
remove_surrounding_whitespace_nodes(node.alternate.nodes);
|
||||
|
||||
elseblock = {
|
||||
type: 'ElseBlock',
|
||||
start,
|
||||
end: end,
|
||||
children: node.alternate.nodes.map(
|
||||
(child) => /** @type {Legacy.LegacyElementLike} */ (visit(child))
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
const start = node.elseif
|
||||
? node.consequent.nodes[0]?.start ??
|
||||
source.lastIndexOf('{', /** @type {number} */ (node.end) - 1)
|
||||
: node.start;
|
||||
|
||||
remove_surrounding_whitespace_nodes(node.consequent.nodes);
|
||||
|
||||
return {
|
||||
type: 'IfBlock',
|
||||
start,
|
||||
end: node.end,
|
||||
expression: node.test,
|
||||
children: node.consequent.nodes.map(
|
||||
(child) => /** @type {Legacy.LegacyElementLike} */ (visit(child))
|
||||
),
|
||||
else: elseblock,
|
||||
elseif: node.elseif ? true : undefined
|
||||
};
|
||||
},
|
||||
OnDirective(node) {
|
||||
return { ...node, type: 'EventHandler' };
|
||||
},
|
||||
// @ts-expect-error
|
||||
SnippetBlock(node, { visit }) {
|
||||
remove_surrounding_whitespace_nodes(node.body.nodes);
|
||||
return {
|
||||
type: 'SnippetBlock',
|
||||
start: node.start,
|
||||
end: node.end,
|
||||
expression: node.expression,
|
||||
parameters: node.parameters,
|
||||
children: node.body.nodes.map((child) => visit(child))
|
||||
};
|
||||
},
|
||||
// @ts-expect-error
|
||||
SvelteBoundary(node, { visit }) {
|
||||
remove_surrounding_whitespace_nodes(node.fragment.nodes);
|
||||
return {
|
||||
type: 'SvelteBoundary',
|
||||
name: 'svelte:boundary',
|
||||
start: node.start,
|
||||
end: node.end,
|
||||
attributes: node.attributes.map(
|
||||
(child) => /** @type {Legacy.LegacyAttributeLike} */ (visit(child))
|
||||
),
|
||||
children: node.fragment.nodes.map((child) => visit(child))
|
||||
};
|
||||
},
|
||||
RegularElement(node, { visit }) {
|
||||
return {
|
||||
type: 'Element',
|
||||
start: node.start,
|
||||
end: node.end,
|
||||
name: node.name,
|
||||
attributes: node.attributes.map((child) => visit(child)),
|
||||
children: node.fragment.nodes.map((child) => visit(child))
|
||||
};
|
||||
},
|
||||
SlotElement(node, { visit }) {
|
||||
return {
|
||||
type: 'Slot',
|
||||
start: node.start,
|
||||
end: node.end,
|
||||
name: node.name,
|
||||
attributes: node.attributes.map(
|
||||
(child) => /** @type {Legacy.LegacyAttributeLike} */ (visit(child))
|
||||
),
|
||||
children: node.fragment.nodes.map(
|
||||
(child) => /** @type {Legacy.LegacyElementLike} */ (visit(child))
|
||||
)
|
||||
};
|
||||
},
|
||||
Attribute(node, { visit, next, path }) {
|
||||
if (node.value !== true && !Array.isArray(node.value)) {
|
||||
path.push(node);
|
||||
const value = /** @type {Legacy.LegacyAttribute['value']} */ ([visit(node.value)]);
|
||||
path.pop();
|
||||
|
||||
return {
|
||||
...node,
|
||||
value
|
||||
};
|
||||
} else {
|
||||
return next();
|
||||
}
|
||||
},
|
||||
StyleDirective(node, { visit, next, path }) {
|
||||
if (node.value !== true && !Array.isArray(node.value)) {
|
||||
path.push(node);
|
||||
const value = /** @type {Legacy.LegacyStyleDirective['value']} */ ([visit(node.value)]);
|
||||
path.pop();
|
||||
|
||||
return {
|
||||
...node,
|
||||
value
|
||||
};
|
||||
} else {
|
||||
return next();
|
||||
}
|
||||
},
|
||||
SpreadAttribute(node) {
|
||||
return { ...node, type: 'Spread' };
|
||||
},
|
||||
StyleSheet(node, context) {
|
||||
return {
|
||||
...node,
|
||||
...context.next(),
|
||||
type: 'Style'
|
||||
};
|
||||
},
|
||||
SvelteBody(node, { visit }) {
|
||||
return {
|
||||
type: 'Body',
|
||||
name: 'svelte:body',
|
||||
start: node.start,
|
||||
end: node.end,
|
||||
attributes: node.attributes.map(
|
||||
(child) => /** @type {Legacy.LegacyAttributeLike} */ (visit(child))
|
||||
),
|
||||
children: node.fragment.nodes.map(
|
||||
(child) => /** @type {Legacy.LegacyElementLike} */ (visit(child))
|
||||
)
|
||||
};
|
||||
},
|
||||
SvelteComponent(node, { visit }) {
|
||||
return {
|
||||
type: 'InlineComponent',
|
||||
name: 'svelte:component',
|
||||
start: node.start,
|
||||
end: node.end,
|
||||
expression: node.expression,
|
||||
attributes: node.attributes.map(
|
||||
(child) => /** @type {Legacy.LegacyAttributeLike} */ (visit(child))
|
||||
),
|
||||
children: node.fragment.nodes.map(
|
||||
(child) => /** @type {Legacy.LegacyElementLike} */ (visit(child))
|
||||
)
|
||||
};
|
||||
},
|
||||
SvelteDocument(node, { visit }) {
|
||||
return {
|
||||
type: 'Document',
|
||||
name: 'svelte:document',
|
||||
start: node.start,
|
||||
end: node.end,
|
||||
attributes: node.attributes.map(
|
||||
(child) => /** @type {Legacy.LegacyAttributeLike} */ (visit(child))
|
||||
),
|
||||
children: node.fragment.nodes.map(
|
||||
(child) => /** @type {Legacy.LegacyElementLike} */ (visit(child))
|
||||
)
|
||||
};
|
||||
},
|
||||
SvelteElement(node, { visit }) {
|
||||
/** @type {Expression | string} */
|
||||
let tag = node.tag;
|
||||
if (
|
||||
tag.type === 'Literal' &&
|
||||
typeof tag.value === 'string' &&
|
||||
source[/** @type {number} */ (node.tag.start) - 1] !== '{'
|
||||
) {
|
||||
tag = tag.value;
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'Element',
|
||||
name: 'svelte:element',
|
||||
start: node.start,
|
||||
end: node.end,
|
||||
tag,
|
||||
attributes: node.attributes.map((child) => visit(child)),
|
||||
children: node.fragment.nodes.map((child) => visit(child))
|
||||
};
|
||||
},
|
||||
SvelteFragment(node, { visit }) {
|
||||
return {
|
||||
type: 'SlotTemplate',
|
||||
name: 'svelte:fragment',
|
||||
start: node.start,
|
||||
end: node.end,
|
||||
attributes: node.attributes.map(
|
||||
(a) => /** @type {Legacy.LegacyAttributeLike} */ (visit(a))
|
||||
),
|
||||
children: node.fragment.nodes.map(
|
||||
(child) => /** @type {Legacy.LegacyElementLike} */ (visit(child))
|
||||
)
|
||||
};
|
||||
},
|
||||
SvelteHead(node, { visit }) {
|
||||
return {
|
||||
type: 'Head',
|
||||
name: 'svelte:head',
|
||||
start: node.start,
|
||||
end: node.end,
|
||||
attributes: node.attributes.map(
|
||||
(child) => /** @type {Legacy.LegacyAttributeLike} */ (visit(child))
|
||||
),
|
||||
children: node.fragment.nodes.map(
|
||||
(child) => /** @type {Legacy.LegacyElementLike} */ (visit(child))
|
||||
)
|
||||
};
|
||||
},
|
||||
SvelteOptions(node, { visit }) {
|
||||
return {
|
||||
type: 'Options',
|
||||
name: 'svelte:options',
|
||||
start: node.start,
|
||||
end: node.end,
|
||||
attributes: node.attributes.map(
|
||||
(child) => /** @type {Legacy.LegacyAttributeLike} */ (visit(child))
|
||||
)
|
||||
};
|
||||
},
|
||||
SvelteSelf(node, { visit }) {
|
||||
return {
|
||||
type: 'InlineComponent',
|
||||
name: 'svelte:self',
|
||||
start: node.start,
|
||||
end: node.end,
|
||||
attributes: node.attributes.map(
|
||||
(child) => /** @type {Legacy.LegacyAttributeLike} */ (visit(child))
|
||||
),
|
||||
children: node.fragment.nodes.map(
|
||||
(child) => /** @type {Legacy.LegacyElementLike} */ (visit(child))
|
||||
)
|
||||
};
|
||||
},
|
||||
SvelteWindow(node, { visit }) {
|
||||
return {
|
||||
type: 'Window',
|
||||
name: 'svelte:window',
|
||||
start: node.start,
|
||||
end: node.end,
|
||||
attributes: node.attributes.map(
|
||||
(child) => /** @type {Legacy.LegacyAttributeLike} */ (visit(child))
|
||||
),
|
||||
children: node.fragment.nodes.map(
|
||||
(child) => /** @type {Legacy.LegacyElementLike} */ (visit(child))
|
||||
)
|
||||
};
|
||||
},
|
||||
Text(node, { path }) {
|
||||
const parent = path.at(-1);
|
||||
if (parent?.type === 'RegularElement' && parent.name === 'style') {
|
||||
// these text nodes are missing `raw` for some dumb reason
|
||||
return /** @type {AST.Text} */ ({
|
||||
type: 'Text',
|
||||
start: node.start,
|
||||
end: node.end,
|
||||
data: node.data
|
||||
});
|
||||
}
|
||||
},
|
||||
TitleElement(node, { visit }) {
|
||||
return {
|
||||
type: 'Title',
|
||||
name: 'title',
|
||||
start: node.start,
|
||||
end: node.end,
|
||||
attributes: node.attributes.map(
|
||||
(child) => /** @type {Legacy.LegacyAttributeLike} */ (visit(child))
|
||||
),
|
||||
children: node.fragment.nodes.map(
|
||||
(child) => /** @type {Legacy.LegacyElementLike} */ (visit(child))
|
||||
)
|
||||
};
|
||||
},
|
||||
TransitionDirective(node) {
|
||||
return { ...node, type: 'Transition' };
|
||||
},
|
||||
UseDirective(node) {
|
||||
return { ...node, type: 'Action' };
|
||||
},
|
||||
LetDirective(node) {
|
||||
return { ...node, type: 'Let' };
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
1989
node_modules/svelte/src/compiler/migrate/index.js
generated
vendored
Normal file
1989
node_modules/svelte/src/compiler/migrate/index.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
214
node_modules/svelte/src/compiler/phases/1-parse/acorn.js
generated
vendored
Normal file
214
node_modules/svelte/src/compiler/phases/1-parse/acorn.js
generated
vendored
Normal file
@@ -0,0 +1,214 @@
|
||||
/** @import { Comment, Program } from 'estree' */
|
||||
/** @import { Node } from 'acorn' */
|
||||
import * as acorn from 'acorn';
|
||||
import { walk } from 'zimmerframe';
|
||||
import { tsPlugin } from 'acorn-typescript';
|
||||
import { locator } from '../../state.js';
|
||||
|
||||
const ParserWithTS = acorn.Parser.extend(tsPlugin({ allowSatisfies: true }));
|
||||
|
||||
/**
|
||||
* @param {string} source
|
||||
* @param {boolean} typescript
|
||||
* @param {boolean} [is_script]
|
||||
*/
|
||||
export function parse(source, typescript, is_script) {
|
||||
const parser = typescript ? ParserWithTS : acorn.Parser;
|
||||
const { onComment, add_comments } = get_comment_handlers(source);
|
||||
// @ts-ignore
|
||||
const parse_statement = parser.prototype.parseStatement;
|
||||
|
||||
// If we're dealing with a <script> then it might contain an export
|
||||
// for something that doesn't exist directly inside but is inside the
|
||||
// component instead, so we need to ensure that Acorn doesn't throw
|
||||
// an error in these cases
|
||||
if (is_script) {
|
||||
// @ts-ignore
|
||||
parser.prototype.parseStatement = function (...args) {
|
||||
const v = parse_statement.call(this, ...args);
|
||||
// @ts-ignore
|
||||
this.undefinedExports = {};
|
||||
return v;
|
||||
};
|
||||
}
|
||||
|
||||
let ast;
|
||||
|
||||
try {
|
||||
ast = parser.parse(source, {
|
||||
onComment,
|
||||
sourceType: 'module',
|
||||
ecmaVersion: 13,
|
||||
locations: true
|
||||
});
|
||||
} finally {
|
||||
if (is_script) {
|
||||
// @ts-ignore
|
||||
parser.prototype.parseStatement = parse_statement;
|
||||
}
|
||||
}
|
||||
|
||||
if (typescript) amend(source, ast);
|
||||
add_comments(ast);
|
||||
|
||||
return /** @type {Program} */ (ast);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} source
|
||||
* @param {boolean} typescript
|
||||
* @param {number} index
|
||||
* @returns {acorn.Expression & { leadingComments?: CommentWithLocation[]; trailingComments?: CommentWithLocation[]; }}
|
||||
*/
|
||||
export function parse_expression_at(source, typescript, index) {
|
||||
const parser = typescript ? ParserWithTS : acorn.Parser;
|
||||
const { onComment, add_comments } = get_comment_handlers(source);
|
||||
|
||||
const ast = parser.parseExpressionAt(source, index, {
|
||||
onComment,
|
||||
sourceType: 'module',
|
||||
ecmaVersion: 13,
|
||||
locations: true
|
||||
});
|
||||
|
||||
if (typescript) amend(source, ast);
|
||||
add_comments(ast);
|
||||
|
||||
return ast;
|
||||
}
|
||||
|
||||
/**
|
||||
* Acorn doesn't add comments to the AST by itself. This factory returns the capabilities
|
||||
* to add them after the fact. They are needed in order to support `svelte-ignore` comments
|
||||
* in JS code and so that `prettier-plugin-svelte` doesn't remove all comments when formatting.
|
||||
* @param {string} source
|
||||
*/
|
||||
function get_comment_handlers(source) {
|
||||
/**
|
||||
* @typedef {Comment & {
|
||||
* start: number;
|
||||
* end: number;
|
||||
* }} CommentWithLocation
|
||||
*/
|
||||
|
||||
/** @type {CommentWithLocation[]} */
|
||||
const comments = [];
|
||||
|
||||
return {
|
||||
/**
|
||||
* @param {boolean} block
|
||||
* @param {string} value
|
||||
* @param {number} start
|
||||
* @param {number} end
|
||||
*/
|
||||
onComment: (block, value, start, end) => {
|
||||
if (block && /\n/.test(value)) {
|
||||
let a = start;
|
||||
while (a > 0 && source[a - 1] !== '\n') a -= 1;
|
||||
|
||||
let b = a;
|
||||
while (/[ \t]/.test(source[b])) b += 1;
|
||||
|
||||
const indentation = source.slice(a, b);
|
||||
value = value.replace(new RegExp(`^${indentation}`, 'gm'), '');
|
||||
}
|
||||
|
||||
comments.push({ type: block ? 'Block' : 'Line', value, start, end });
|
||||
},
|
||||
|
||||
/** @param {acorn.Node & { leadingComments?: CommentWithLocation[]; trailingComments?: CommentWithLocation[]; }} ast */
|
||||
add_comments(ast) {
|
||||
if (comments.length === 0) return;
|
||||
|
||||
walk(ast, null, {
|
||||
_(node, { next, path }) {
|
||||
let comment;
|
||||
|
||||
while (comments[0] && comments[0].start < node.start) {
|
||||
comment = /** @type {CommentWithLocation} */ (comments.shift());
|
||||
(node.leadingComments ||= []).push(comment);
|
||||
}
|
||||
|
||||
next();
|
||||
|
||||
if (comments[0]) {
|
||||
const parent = /** @type {any} */ (path.at(-1));
|
||||
|
||||
if (parent === undefined || node.end !== parent.end) {
|
||||
const slice = source.slice(node.end, comments[0].start);
|
||||
const is_last_in_body =
|
||||
((parent?.type === 'BlockStatement' || parent?.type === 'Program') &&
|
||||
parent.body.indexOf(node) === parent.body.length - 1) ||
|
||||
(parent?.type === 'ArrayExpression' &&
|
||||
parent.elements.indexOf(node) === parent.elements.length - 1) ||
|
||||
(parent?.type === 'ObjectExpression' &&
|
||||
parent.properties.indexOf(node) === parent.properties.length - 1);
|
||||
|
||||
if (is_last_in_body) {
|
||||
// Special case: There can be multiple trailing comments after the last node in a block,
|
||||
// and they can be separated by newlines
|
||||
let end = node.end;
|
||||
|
||||
while (comments.length) {
|
||||
const comment = comments[0];
|
||||
if (parent && comment.start >= parent.end) break;
|
||||
|
||||
(node.trailingComments ||= []).push(comment);
|
||||
comments.shift();
|
||||
end = comment.end;
|
||||
}
|
||||
} else if (node.end <= comments[0].start && /^[,) \t]*$/.test(slice)) {
|
||||
node.trailingComments = [/** @type {CommentWithLocation} */ (comments.shift())];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Special case: Trailing comments after the root node (which can only happen for expression tags or for Program nodes).
|
||||
// Adding them ensures that we can later detect the end of the expression tag correctly.
|
||||
if (comments.length > 0 && (comments[0].start >= ast.end || ast.type === 'Program')) {
|
||||
(ast.trailingComments ||= []).push(...comments.splice(0));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Tidy up some stuff left behind by acorn-typescript
|
||||
* @param {string} source
|
||||
* @param {Node} node
|
||||
*/
|
||||
function amend(source, node) {
|
||||
return walk(node, null, {
|
||||
_(node, context) {
|
||||
// @ts-expect-error
|
||||
delete node.loc.start.index;
|
||||
// @ts-expect-error
|
||||
delete node.loc.end.index;
|
||||
|
||||
if (typeof node.loc?.end === 'number') {
|
||||
const loc = locator(node.loc.end);
|
||||
if (loc) {
|
||||
node.loc.end = {
|
||||
line: loc.line,
|
||||
column: loc.column
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
/** @type {any} */ (node).typeAnnotation &&
|
||||
(node.end === undefined || node.end < node.start)
|
||||
) {
|
||||
// i think there might be a bug in acorn-typescript that prevents
|
||||
// `end` from being assigned when there's a type annotation
|
||||
let end = /** @type {any} */ (node).typeAnnotation.start;
|
||||
while (/\s/.test(source[end - 1])) end -= 1;
|
||||
node.end = end;
|
||||
}
|
||||
|
||||
context.next();
|
||||
}
|
||||
});
|
||||
}
|
||||
3
node_modules/svelte/src/compiler/phases/1-parse/ambient.d.ts
generated
vendored
Normal file
3
node_modules/svelte/src/compiler/phases/1-parse/ambient.d.ts
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
// Silence the acorn typescript errors through this ambient type definition + tsconfig.json path alias
|
||||
// That way we can omit `"skipLibCheck": true` and catch other errors in our d.ts files
|
||||
declare module 'acorn-typescript';
|
||||
312
node_modules/svelte/src/compiler/phases/1-parse/index.js
generated
vendored
Normal file
312
node_modules/svelte/src/compiler/phases/1-parse/index.js
generated
vendored
Normal file
@@ -0,0 +1,312 @@
|
||||
/** @import { AST } from '#compiler' */
|
||||
// @ts-expect-error acorn type definitions are borked in the release we use
|
||||
import { isIdentifierStart, isIdentifierChar } from 'acorn';
|
||||
import fragment from './state/fragment.js';
|
||||
import { regex_whitespace } from '../patterns.js';
|
||||
import * as e from '../../errors.js';
|
||||
import { create_fragment } from './utils/create.js';
|
||||
import read_options from './read/options.js';
|
||||
import { is_reserved } from '../../../utils.js';
|
||||
import { disallow_children } from '../2-analyze/visitors/shared/special-element.js';
|
||||
|
||||
const regex_position_indicator = / \(\d+:\d+\)$/;
|
||||
|
||||
const regex_lang_attribute =
|
||||
/<!--[^]*?-->|<script\s+(?:[^>]*|(?:[^=>'"/]+=(?:"[^"]*"|'[^']*'|[^>\s]+)\s+)*)lang=(["'])?([^"' >]+)\1[^>]*>/g;
|
||||
|
||||
export class Parser {
|
||||
/**
|
||||
* @readonly
|
||||
* @type {string}
|
||||
*/
|
||||
template;
|
||||
|
||||
/**
|
||||
* @readonly
|
||||
* @type {string}
|
||||
*/
|
||||
template_untrimmed;
|
||||
|
||||
/**
|
||||
* Whether or not we're in loose parsing mode, in which
|
||||
* case we try to continue parsing as much as possible
|
||||
* @type {boolean}
|
||||
*/
|
||||
loose;
|
||||
|
||||
/** */
|
||||
index = 0;
|
||||
|
||||
/** Whether we're parsing in TypeScript mode */
|
||||
ts = false;
|
||||
|
||||
/** @type {AST.TemplateNode[]} */
|
||||
stack = [];
|
||||
|
||||
/** @type {AST.Fragment[]} */
|
||||
fragments = [];
|
||||
|
||||
/** @type {AST.Root} */
|
||||
root;
|
||||
|
||||
/** @type {Record<string, boolean>} */
|
||||
meta_tags = {};
|
||||
|
||||
/** @type {LastAutoClosedTag | undefined} */
|
||||
last_auto_closed_tag;
|
||||
|
||||
/**
|
||||
* @param {string} template
|
||||
* @param {boolean} loose
|
||||
*/
|
||||
constructor(template, loose) {
|
||||
if (typeof template !== 'string') {
|
||||
throw new TypeError('Template must be a string');
|
||||
}
|
||||
|
||||
this.loose = loose;
|
||||
this.template_untrimmed = template;
|
||||
this.template = template.trimEnd();
|
||||
|
||||
let match_lang;
|
||||
|
||||
do match_lang = regex_lang_attribute.exec(template);
|
||||
while (match_lang && match_lang[0][1] !== 's'); // ensure it starts with '<s' to match script tags
|
||||
|
||||
regex_lang_attribute.lastIndex = 0; // reset matched index to pass tests - otherwise declare the regex inside the constructor
|
||||
|
||||
this.ts = match_lang?.[2] === 'ts';
|
||||
|
||||
this.root = {
|
||||
css: null,
|
||||
js: [],
|
||||
// @ts-ignore
|
||||
start: null,
|
||||
// @ts-ignore
|
||||
end: null,
|
||||
type: 'Root',
|
||||
fragment: create_fragment(),
|
||||
options: null,
|
||||
metadata: {
|
||||
ts: this.ts
|
||||
}
|
||||
};
|
||||
|
||||
this.stack.push(this.root);
|
||||
this.fragments.push(this.root.fragment);
|
||||
|
||||
/** @type {ParserState} */
|
||||
let state = fragment;
|
||||
|
||||
while (this.index < this.template.length) {
|
||||
state = state(this) || fragment;
|
||||
}
|
||||
|
||||
if (this.stack.length > 1) {
|
||||
const current = this.current();
|
||||
|
||||
if (this.loose) {
|
||||
current.end = this.template.length;
|
||||
} else if (current.type === 'RegularElement') {
|
||||
current.end = current.start + 1;
|
||||
e.element_unclosed(current, current.name);
|
||||
} else {
|
||||
current.end = current.start + 1;
|
||||
e.block_unclosed(current);
|
||||
}
|
||||
}
|
||||
|
||||
if (state !== fragment) {
|
||||
e.unexpected_eof(this.index);
|
||||
}
|
||||
|
||||
if (this.root.fragment.nodes.length) {
|
||||
let start = /** @type {number} */ (this.root.fragment.nodes[0].start);
|
||||
while (regex_whitespace.test(template[start])) start += 1;
|
||||
|
||||
let end = /** @type {number} */ (
|
||||
this.root.fragment.nodes[this.root.fragment.nodes.length - 1].end
|
||||
);
|
||||
while (regex_whitespace.test(template[end - 1])) end -= 1;
|
||||
|
||||
this.root.start = start;
|
||||
this.root.end = end;
|
||||
} else {
|
||||
// @ts-ignore
|
||||
this.root.start = this.root.end = null;
|
||||
}
|
||||
|
||||
const options_index = this.root.fragment.nodes.findIndex(
|
||||
/** @param {any} thing */
|
||||
(thing) => thing.type === 'SvelteOptions'
|
||||
);
|
||||
if (options_index !== -1) {
|
||||
const options = /** @type {AST.SvelteOptionsRaw} */ (this.root.fragment.nodes[options_index]);
|
||||
this.root.fragment.nodes.splice(options_index, 1);
|
||||
this.root.options = read_options(options);
|
||||
|
||||
disallow_children(options);
|
||||
|
||||
// We need this for the old AST format
|
||||
Object.defineProperty(this.root.options, '__raw__', {
|
||||
value: options,
|
||||
enumerable: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
current() {
|
||||
return this.stack[this.stack.length - 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {any} err
|
||||
* @returns {never}
|
||||
*/
|
||||
acorn_error(err) {
|
||||
e.js_parse_error(err.pos, err.message.replace(regex_position_indicator, ''));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} str
|
||||
* @param {boolean} required
|
||||
* @param {boolean} required_in_loose
|
||||
*/
|
||||
eat(str, required = false, required_in_loose = true) {
|
||||
if (this.match(str)) {
|
||||
this.index += str.length;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (required && (!this.loose || required_in_loose)) {
|
||||
e.expected_token(this.index, str);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @param {string} str */
|
||||
match(str) {
|
||||
const length = str.length;
|
||||
if (length === 1) {
|
||||
// more performant than slicing
|
||||
return this.template[this.index] === str;
|
||||
}
|
||||
|
||||
return this.template.slice(this.index, this.index + length) === str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Match a regex at the current index
|
||||
* @param {RegExp} pattern Should have a ^ anchor at the start so the regex doesn't search past the beginning, resulting in worse performance
|
||||
*/
|
||||
match_regex(pattern) {
|
||||
const match = pattern.exec(this.template.slice(this.index));
|
||||
if (!match || match.index !== 0) return null;
|
||||
|
||||
return match[0];
|
||||
}
|
||||
|
||||
allow_whitespace() {
|
||||
while (this.index < this.template.length && regex_whitespace.test(this.template[this.index])) {
|
||||
this.index++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for a regex starting at the current index and return the result if it matches
|
||||
* @param {RegExp} pattern Should have a ^ anchor at the start so the regex doesn't search past the beginning, resulting in worse performance
|
||||
*/
|
||||
read(pattern) {
|
||||
const result = this.match_regex(pattern);
|
||||
if (result) this.index += result.length;
|
||||
return result;
|
||||
}
|
||||
|
||||
/** @param {any} allow_reserved */
|
||||
read_identifier(allow_reserved = false) {
|
||||
const start = this.index;
|
||||
|
||||
let i = this.index;
|
||||
|
||||
const code = /** @type {number} */ (this.template.codePointAt(i));
|
||||
if (!isIdentifierStart(code, true)) return null;
|
||||
|
||||
i += code <= 0xffff ? 1 : 2;
|
||||
|
||||
while (i < this.template.length) {
|
||||
const code = /** @type {number} */ (this.template.codePointAt(i));
|
||||
|
||||
if (!isIdentifierChar(code, true)) break;
|
||||
i += code <= 0xffff ? 1 : 2;
|
||||
}
|
||||
|
||||
const identifier = this.template.slice(this.index, (this.index = i));
|
||||
|
||||
if (!allow_reserved && is_reserved(identifier)) {
|
||||
e.unexpected_reserved_word(start, identifier);
|
||||
}
|
||||
|
||||
return identifier;
|
||||
}
|
||||
|
||||
/** @param {RegExp} pattern */
|
||||
read_until(pattern) {
|
||||
if (this.index >= this.template.length) {
|
||||
if (this.loose) return '';
|
||||
e.unexpected_eof(this.template.length);
|
||||
}
|
||||
|
||||
const start = this.index;
|
||||
const match = pattern.exec(this.template.slice(start));
|
||||
|
||||
if (match) {
|
||||
this.index = start + match.index;
|
||||
return this.template.slice(start, this.index);
|
||||
}
|
||||
|
||||
this.index = this.template.length;
|
||||
return this.template.slice(start);
|
||||
}
|
||||
|
||||
require_whitespace() {
|
||||
if (!regex_whitespace.test(this.template[this.index])) {
|
||||
e.expected_whitespace(this.index);
|
||||
}
|
||||
|
||||
this.allow_whitespace();
|
||||
}
|
||||
|
||||
pop() {
|
||||
this.fragments.pop();
|
||||
return this.stack.pop();
|
||||
}
|
||||
|
||||
/**
|
||||
* @template {AST.Fragment['nodes'][number]} T
|
||||
* @param {T} node
|
||||
* @returns {T}
|
||||
*/
|
||||
append(node) {
|
||||
this.fragments.at(-1)?.nodes.push(node);
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} template
|
||||
* @param {boolean} [loose]
|
||||
* @returns {AST.Root}
|
||||
*/
|
||||
export function parse(template, loose = false) {
|
||||
const parser = new Parser(template, loose);
|
||||
return parser.root;
|
||||
}
|
||||
|
||||
/** @typedef {(parser: Parser) => ParserState | void} ParserState */
|
||||
|
||||
/** @typedef {Object} LastAutoClosedTag
|
||||
* @property {string} tag
|
||||
* @property {string} reason
|
||||
* @property {number} depth
|
||||
*/
|
||||
187
node_modules/svelte/src/compiler/phases/1-parse/read/context.js
generated
vendored
Normal file
187
node_modules/svelte/src/compiler/phases/1-parse/read/context.js
generated
vendored
Normal file
@@ -0,0 +1,187 @@
|
||||
/** @import { Location } from 'locate-character' */
|
||||
/** @import { Pattern } from 'estree' */
|
||||
/** @import { Parser } from '../index.js' */
|
||||
import { is_bracket_open, is_bracket_close, get_bracket_close } from '../utils/bracket.js';
|
||||
import { parse_expression_at } from '../acorn.js';
|
||||
import { regex_not_newline_characters } from '../../patterns.js';
|
||||
import * as e from '../../../errors.js';
|
||||
import { locator } from '../../../state.js';
|
||||
|
||||
/**
|
||||
* @param {Parser} parser
|
||||
* @returns {Pattern}
|
||||
*/
|
||||
export default function read_pattern(parser) {
|
||||
const start = parser.index;
|
||||
let i = parser.index;
|
||||
|
||||
const name = parser.read_identifier();
|
||||
|
||||
if (name !== null) {
|
||||
const annotation = read_type_annotation(parser);
|
||||
|
||||
return {
|
||||
type: 'Identifier',
|
||||
name,
|
||||
start,
|
||||
loc: {
|
||||
start: /** @type {Location} */ (locator(start)),
|
||||
end: /** @type {Location} */ (locator(parser.index))
|
||||
},
|
||||
end: parser.index,
|
||||
typeAnnotation: annotation
|
||||
};
|
||||
}
|
||||
|
||||
if (!is_bracket_open(parser.template[i])) {
|
||||
e.expected_pattern(i);
|
||||
}
|
||||
|
||||
i = match_bracket(parser, start);
|
||||
parser.index = i;
|
||||
|
||||
const pattern_string = parser.template.slice(start, i);
|
||||
|
||||
try {
|
||||
// the length of the `space_with_newline` has to be start - 1
|
||||
// because we added a `(` in front of the pattern_string,
|
||||
// which shifted the entire string to right by 1
|
||||
// so we offset it by removing 1 character in the `space_with_newline`
|
||||
// to achieve that, we remove the 1st space encountered,
|
||||
// so it will not affect the `column` of the node
|
||||
let space_with_newline = parser.template
|
||||
.slice(0, start)
|
||||
.replace(regex_not_newline_characters, ' ');
|
||||
const first_space = space_with_newline.indexOf(' ');
|
||||
space_with_newline =
|
||||
space_with_newline.slice(0, first_space) + space_with_newline.slice(first_space + 1);
|
||||
|
||||
const expression = /** @type {any} */ (
|
||||
parse_expression_at(`${space_with_newline}(${pattern_string} = 1)`, parser.ts, start - 1)
|
||||
).left;
|
||||
|
||||
expression.typeAnnotation = read_type_annotation(parser);
|
||||
if (expression.typeAnnotation) {
|
||||
expression.end = expression.typeAnnotation.end;
|
||||
}
|
||||
|
||||
return expression;
|
||||
} catch (error) {
|
||||
parser.acorn_error(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Parser} parser
|
||||
* @param {number} start
|
||||
*/
|
||||
function match_bracket(parser, start) {
|
||||
const bracket_stack = [];
|
||||
|
||||
let i = start;
|
||||
|
||||
while (i < parser.template.length) {
|
||||
let char = parser.template[i++];
|
||||
|
||||
if (char === "'" || char === '"' || char === '`') {
|
||||
i = match_quote(parser, i, char);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_bracket_open(char)) {
|
||||
bracket_stack.push(char);
|
||||
} else if (is_bracket_close(char)) {
|
||||
const popped = /** @type {string} */ (bracket_stack.pop());
|
||||
const expected = /** @type {string} */ (get_bracket_close(popped));
|
||||
|
||||
if (char !== expected) {
|
||||
e.expected_token(i - 1, expected);
|
||||
}
|
||||
|
||||
if (bracket_stack.length === 0) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
e.unexpected_eof(parser.template.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Parser} parser
|
||||
* @param {number} start
|
||||
* @param {string} quote
|
||||
*/
|
||||
function match_quote(parser, start, quote) {
|
||||
let is_escaped = false;
|
||||
let i = start;
|
||||
|
||||
while (i < parser.template.length) {
|
||||
const char = parser.template[i++];
|
||||
|
||||
if (is_escaped) {
|
||||
is_escaped = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (char === quote) {
|
||||
return i;
|
||||
}
|
||||
|
||||
if (char === '\\') {
|
||||
is_escaped = true;
|
||||
}
|
||||
|
||||
if (quote === '`' && char === '$' && parser.template[i] === '{') {
|
||||
i = match_bracket(parser, i);
|
||||
}
|
||||
}
|
||||
|
||||
e.unterminated_string_constant(start);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Parser} parser
|
||||
* @returns {any}
|
||||
*/
|
||||
function read_type_annotation(parser) {
|
||||
const start = parser.index;
|
||||
parser.allow_whitespace();
|
||||
|
||||
if (!parser.eat(':')) {
|
||||
parser.index = start;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// we need to trick Acorn into parsing the type annotation
|
||||
const insert = '_ as ';
|
||||
let a = parser.index - insert.length;
|
||||
const template =
|
||||
parser.template.slice(0, a).replace(/[^\n]/g, ' ') +
|
||||
insert +
|
||||
// If this is a type annotation for a function parameter, Acorn-TS will treat subsequent
|
||||
// parameters as part of a sequence expression instead, and will then error on optional
|
||||
// parameters (`?:`). Therefore replace that sequence with something that will not error.
|
||||
parser.template.slice(parser.index).replace(/\?\s*:/g, ':');
|
||||
let expression = parse_expression_at(template, parser.ts, a);
|
||||
|
||||
// `foo: bar = baz` gets mangled — fix it
|
||||
if (expression.type === 'AssignmentExpression') {
|
||||
let b = expression.right.start;
|
||||
while (template[b] !== '=') b -= 1;
|
||||
expression = parse_expression_at(template.slice(0, b), parser.ts, a);
|
||||
}
|
||||
|
||||
// `array as item: string, index` becomes `string, index`, which is mistaken as a sequence expression - fix that
|
||||
if (expression.type === 'SequenceExpression') {
|
||||
expression = expression.expressions[0];
|
||||
}
|
||||
|
||||
parser.index = /** @type {number} */ (expression.end);
|
||||
return {
|
||||
type: 'TSTypeAnnotation',
|
||||
start,
|
||||
end: parser.index,
|
||||
typeAnnotation: /** @type {any} */ (expression).typeAnnotation
|
||||
};
|
||||
}
|
||||
81
node_modules/svelte/src/compiler/phases/1-parse/read/expression.js
generated
vendored
Normal file
81
node_modules/svelte/src/compiler/phases/1-parse/read/expression.js
generated
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
/** @import { Expression } from 'estree' */
|
||||
/** @import { Parser } from '../index.js' */
|
||||
import { parse_expression_at } from '../acorn.js';
|
||||
import { regex_whitespace } from '../../patterns.js';
|
||||
import * as e from '../../../errors.js';
|
||||
import { find_matching_bracket } from '../utils/bracket.js';
|
||||
|
||||
/**
|
||||
* @param {Parser} parser
|
||||
* @param {string} [opening_token]
|
||||
* @returns {Expression | undefined}
|
||||
*/
|
||||
export function get_loose_identifier(parser, opening_token) {
|
||||
// Find the next } and treat it as the end of the expression
|
||||
const end = find_matching_bracket(parser.template, parser.index, opening_token ?? '{');
|
||||
if (end) {
|
||||
const start = parser.index;
|
||||
parser.index = end;
|
||||
// We don't know what the expression is and signal this by returning an empty identifier
|
||||
return {
|
||||
type: 'Identifier',
|
||||
start,
|
||||
end,
|
||||
name: ''
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Parser} parser
|
||||
* @param {string} [opening_token]
|
||||
* @param {boolean} [disallow_loose]
|
||||
* @returns {Expression}
|
||||
*/
|
||||
export default function read_expression(parser, opening_token, disallow_loose) {
|
||||
try {
|
||||
const node = parse_expression_at(parser.template, parser.ts, parser.index);
|
||||
|
||||
let num_parens = 0;
|
||||
|
||||
if (node.leadingComments !== undefined && node.leadingComments.length > 0) {
|
||||
parser.index = node.leadingComments.at(-1).end;
|
||||
}
|
||||
|
||||
for (let i = parser.index; i < /** @type {number} */ (node.start); i += 1) {
|
||||
if (parser.template[i] === '(') num_parens += 1;
|
||||
}
|
||||
|
||||
let index = /** @type {number} */ (node.end);
|
||||
if (node.trailingComments !== undefined && node.trailingComments.length > 0) {
|
||||
index = node.trailingComments.at(-1).end;
|
||||
}
|
||||
|
||||
while (num_parens > 0) {
|
||||
const char = parser.template[index];
|
||||
|
||||
if (char === ')') {
|
||||
num_parens -= 1;
|
||||
} else if (!regex_whitespace.test(char)) {
|
||||
e.expected_token(index, ')');
|
||||
}
|
||||
|
||||
index += 1;
|
||||
}
|
||||
|
||||
parser.index = index;
|
||||
|
||||
return /** @type {Expression} */ (node);
|
||||
} catch (err) {
|
||||
// If we are in an each loop we need the error to be thrown in cases like
|
||||
// `as { y = z }` so we still throw and handle the error there
|
||||
if (parser.loose && !disallow_loose) {
|
||||
const expression = get_loose_identifier(parser, opening_token);
|
||||
if (expression) {
|
||||
return expression;
|
||||
}
|
||||
}
|
||||
|
||||
parser.acorn_error(err);
|
||||
}
|
||||
}
|
||||
261
node_modules/svelte/src/compiler/phases/1-parse/read/options.js
generated
vendored
Normal file
261
node_modules/svelte/src/compiler/phases/1-parse/read/options.js
generated
vendored
Normal file
@@ -0,0 +1,261 @@
|
||||
/** @import { ObjectExpression } from 'estree' */
|
||||
/** @import { AST } from '#compiler' */
|
||||
import { NAMESPACE_MATHML, NAMESPACE_SVG } from '../../../../constants.js';
|
||||
import * as e from '../../../errors.js';
|
||||
|
||||
/**
|
||||
* @param {AST.SvelteOptionsRaw} node
|
||||
* @returns {AST.Root['options']}
|
||||
*/
|
||||
export default function read_options(node) {
|
||||
/** @type {AST.SvelteOptions} */
|
||||
const component_options = {
|
||||
start: node.start,
|
||||
end: node.end,
|
||||
// @ts-ignore
|
||||
attributes: node.attributes
|
||||
};
|
||||
|
||||
if (!node) {
|
||||
return component_options;
|
||||
}
|
||||
|
||||
for (const attribute of node.attributes) {
|
||||
if (attribute.type !== 'Attribute') {
|
||||
e.svelte_options_invalid_attribute(attribute);
|
||||
}
|
||||
|
||||
const { name } = attribute;
|
||||
|
||||
switch (name) {
|
||||
case 'runes': {
|
||||
component_options.runes = get_boolean_value(attribute);
|
||||
break;
|
||||
}
|
||||
case 'tag': {
|
||||
e.svelte_options_deprecated_tag(attribute);
|
||||
break; // eslint doesn't know this is unnecessary
|
||||
}
|
||||
case 'customElement': {
|
||||
/** @type {AST.SvelteOptions['customElement']} */
|
||||
const ce = {};
|
||||
const { value: v } = attribute;
|
||||
const value = v === true || Array.isArray(v) ? v : [v];
|
||||
|
||||
if (value === true) {
|
||||
e.svelte_options_invalid_customelement(attribute);
|
||||
} else if (value[0].type === 'Text') {
|
||||
const tag = get_static_value(attribute);
|
||||
validate_tag(attribute, tag);
|
||||
ce.tag = tag;
|
||||
component_options.customElement = ce;
|
||||
break;
|
||||
} else if (value[0].expression.type !== 'ObjectExpression') {
|
||||
// Before Svelte 4 it was necessary to explicitly set customElement to null or else you'd get a warning.
|
||||
// This is no longer necessary, but for backwards compat just skip in this case now.
|
||||
if (value[0].expression.type === 'Literal' && value[0].expression.value === null) {
|
||||
break;
|
||||
}
|
||||
e.svelte_options_invalid_customelement(attribute);
|
||||
}
|
||||
|
||||
/** @type {Array<[string, any]>} */
|
||||
const properties = [];
|
||||
for (const property of value[0].expression.properties) {
|
||||
if (
|
||||
property.type !== 'Property' ||
|
||||
property.computed ||
|
||||
property.key.type !== 'Identifier'
|
||||
) {
|
||||
e.svelte_options_invalid_customelement(attribute);
|
||||
}
|
||||
properties.push([property.key.name, property.value]);
|
||||
}
|
||||
|
||||
const tag = properties.find(([name]) => name === 'tag');
|
||||
if (tag) {
|
||||
const tag_value = tag[1]?.value;
|
||||
validate_tag(tag, tag_value);
|
||||
ce.tag = tag_value;
|
||||
}
|
||||
|
||||
const props = properties.find(([name]) => name === 'props')?.[1];
|
||||
if (props) {
|
||||
if (props.type !== 'ObjectExpression') {
|
||||
e.svelte_options_invalid_customelement_props(attribute);
|
||||
}
|
||||
ce.props = {};
|
||||
for (const property of /** @type {ObjectExpression} */ (props).properties) {
|
||||
if (
|
||||
property.type !== 'Property' ||
|
||||
property.computed ||
|
||||
property.key.type !== 'Identifier' ||
|
||||
property.value.type !== 'ObjectExpression'
|
||||
) {
|
||||
e.svelte_options_invalid_customelement_props(attribute);
|
||||
}
|
||||
ce.props[property.key.name] = {};
|
||||
for (const prop of property.value.properties) {
|
||||
if (
|
||||
prop.type !== 'Property' ||
|
||||
prop.computed ||
|
||||
prop.key.type !== 'Identifier' ||
|
||||
prop.value.type !== 'Literal'
|
||||
) {
|
||||
e.svelte_options_invalid_customelement_props(attribute);
|
||||
}
|
||||
|
||||
if (prop.key.name === 'type') {
|
||||
if (
|
||||
['String', 'Number', 'Boolean', 'Array', 'Object'].indexOf(
|
||||
/** @type {string} */ (prop.value.value)
|
||||
) === -1
|
||||
) {
|
||||
e.svelte_options_invalid_customelement_props(attribute);
|
||||
}
|
||||
ce.props[property.key.name].type = /** @type {any} */ (prop.value.value);
|
||||
} else if (prop.key.name === 'reflect') {
|
||||
if (typeof prop.value.value !== 'boolean') {
|
||||
e.svelte_options_invalid_customelement_props(attribute);
|
||||
}
|
||||
ce.props[property.key.name].reflect = prop.value.value;
|
||||
} else if (prop.key.name === 'attribute') {
|
||||
if (typeof prop.value.value !== 'string') {
|
||||
e.svelte_options_invalid_customelement_props(attribute);
|
||||
}
|
||||
ce.props[property.key.name].attribute = prop.value.value;
|
||||
} else {
|
||||
e.svelte_options_invalid_customelement_props(attribute);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const shadow = properties.find(([name]) => name === 'shadow')?.[1];
|
||||
if (shadow) {
|
||||
const shadowdom = shadow?.value;
|
||||
if (shadowdom !== 'open' && shadowdom !== 'none') {
|
||||
e.svelte_options_invalid_customelement_shadow(shadow);
|
||||
}
|
||||
ce.shadow = shadowdom;
|
||||
}
|
||||
|
||||
const extend = properties.find(([name]) => name === 'extend')?.[1];
|
||||
if (extend) {
|
||||
ce.extend = extend;
|
||||
}
|
||||
|
||||
component_options.customElement = ce;
|
||||
break;
|
||||
}
|
||||
case 'namespace': {
|
||||
const value = get_static_value(attribute);
|
||||
|
||||
if (value === NAMESPACE_SVG) {
|
||||
component_options.namespace = 'svg';
|
||||
} else if (value === NAMESPACE_MATHML) {
|
||||
component_options.namespace = 'mathml';
|
||||
} else if (value === 'html' || value === 'mathml' || value === 'svg') {
|
||||
component_options.namespace = value;
|
||||
} else {
|
||||
e.svelte_options_invalid_attribute_value(attribute, `"html", "mathml" or "svg"`);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case 'css': {
|
||||
const value = get_static_value(attribute);
|
||||
|
||||
if (value === 'injected') {
|
||||
component_options.css = value;
|
||||
} else {
|
||||
e.svelte_options_invalid_attribute_value(attribute, `"injected"`);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case 'immutable': {
|
||||
component_options.immutable = get_boolean_value(attribute);
|
||||
break;
|
||||
}
|
||||
case 'preserveWhitespace': {
|
||||
component_options.preserveWhitespace = get_boolean_value(attribute);
|
||||
break;
|
||||
}
|
||||
case 'accessors': {
|
||||
component_options.accessors = get_boolean_value(attribute);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
e.svelte_options_unknown_attribute(attribute, name);
|
||||
}
|
||||
}
|
||||
|
||||
return component_options;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {any} attribute
|
||||
*/
|
||||
function get_static_value(attribute) {
|
||||
const { value } = attribute;
|
||||
|
||||
if (value === true) return true;
|
||||
|
||||
const chunk = Array.isArray(value) ? value[0] : value;
|
||||
|
||||
if (!chunk) return true;
|
||||
if (value.length > 1) {
|
||||
return null;
|
||||
}
|
||||
if (chunk.type === 'Text') return chunk.data;
|
||||
if (chunk.expression.type !== 'Literal') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return chunk.expression.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {any} attribute
|
||||
*/
|
||||
function get_boolean_value(attribute) {
|
||||
const value = get_static_value(attribute);
|
||||
if (typeof value !== 'boolean') {
|
||||
e.svelte_options_invalid_attribute_value(attribute, 'true or false');
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name
|
||||
const tag_name_char =
|
||||
'[a-z0-9_.\xB7\xC0-\xD6\xD8-\xF6\xF8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\u{10000}-\u{EFFFF}-]';
|
||||
const regex_valid_tag_name = new RegExp(`^[a-z]${tag_name_char}*-${tag_name_char}*$`, 'u');
|
||||
const reserved_tag_names = [
|
||||
'annotation-xml',
|
||||
'color-profile',
|
||||
'font-face',
|
||||
'font-face-src',
|
||||
'font-face-uri',
|
||||
'font-face-format',
|
||||
'font-face-name',
|
||||
'missing-glyph'
|
||||
];
|
||||
|
||||
/**
|
||||
* @param {any} attribute
|
||||
* @param {string | null} tag
|
||||
* @returns {asserts tag is string}
|
||||
*/
|
||||
function validate_tag(attribute, tag) {
|
||||
if (typeof tag !== 'string') {
|
||||
e.svelte_options_invalid_tagname(attribute);
|
||||
}
|
||||
if (tag) {
|
||||
if (!regex_valid_tag_name.test(tag)) {
|
||||
e.svelte_options_invalid_tagname(attribute);
|
||||
} else if (reserved_tag_names.includes(tag)) {
|
||||
e.svelte_options_reserved_tagname(attribute);
|
||||
}
|
||||
}
|
||||
}
|
||||
90
node_modules/svelte/src/compiler/phases/1-parse/read/script.js
generated
vendored
Normal file
90
node_modules/svelte/src/compiler/phases/1-parse/read/script.js
generated
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
/** @import { Program } from 'estree' */
|
||||
/** @import { AST } from '#compiler' */
|
||||
/** @import { Parser } from '../index.js' */
|
||||
import * as acorn from '../acorn.js';
|
||||
import { regex_not_newline_characters } from '../../patterns.js';
|
||||
import * as e from '../../../errors.js';
|
||||
import * as w from '../../../warnings.js';
|
||||
import { is_text_attribute } from '../../../utils/ast.js';
|
||||
|
||||
const regex_closing_script_tag = /<\/script\s*>/;
|
||||
const regex_starts_with_closing_script_tag = /^<\/script\s*>/;
|
||||
|
||||
const RESERVED_ATTRIBUTES = ['server', 'client', 'worker', 'test', 'default'];
|
||||
const ALLOWED_ATTRIBUTES = ['context', 'generics', 'lang', 'module'];
|
||||
|
||||
/**
|
||||
* @param {Parser} parser
|
||||
* @param {number} start
|
||||
* @param {Array<AST.Attribute | AST.SpreadAttribute | AST.Directive>} attributes
|
||||
* @returns {AST.Script}
|
||||
*/
|
||||
export function read_script(parser, start, attributes) {
|
||||
const script_start = parser.index;
|
||||
const data = parser.read_until(regex_closing_script_tag);
|
||||
if (parser.index >= parser.template.length) {
|
||||
e.element_unclosed(parser.template.length, 'script');
|
||||
}
|
||||
|
||||
const source =
|
||||
parser.template.slice(0, script_start).replace(regex_not_newline_characters, ' ') + data;
|
||||
parser.read(regex_starts_with_closing_script_tag);
|
||||
|
||||
/** @type {Program} */
|
||||
let ast;
|
||||
|
||||
try {
|
||||
ast = acorn.parse(source, parser.ts, true);
|
||||
} catch (err) {
|
||||
parser.acorn_error(err);
|
||||
}
|
||||
|
||||
// TODO is this necessary?
|
||||
ast.start = script_start;
|
||||
|
||||
/** @type {'default' | 'module'} */
|
||||
let context = 'default';
|
||||
|
||||
for (const attribute of /** @type {AST.Attribute[]} */ (attributes)) {
|
||||
if (RESERVED_ATTRIBUTES.includes(attribute.name)) {
|
||||
e.script_reserved_attribute(attribute, attribute.name);
|
||||
}
|
||||
|
||||
if (!ALLOWED_ATTRIBUTES.includes(attribute.name)) {
|
||||
w.script_unknown_attribute(attribute);
|
||||
}
|
||||
|
||||
if (attribute.name === 'module') {
|
||||
if (attribute.value !== true) {
|
||||
// Deliberately a generic code to future-proof for potential other attributes
|
||||
e.script_invalid_attribute_value(attribute, attribute.name);
|
||||
}
|
||||
|
||||
context = 'module';
|
||||
}
|
||||
|
||||
if (attribute.name === 'context') {
|
||||
if (attribute.value === true || !is_text_attribute(attribute)) {
|
||||
e.script_invalid_context(attribute);
|
||||
}
|
||||
|
||||
const value = attribute.value[0].data;
|
||||
|
||||
if (value !== 'module') {
|
||||
e.script_invalid_context(attribute);
|
||||
}
|
||||
|
||||
context = 'module';
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'Script',
|
||||
start,
|
||||
end: parser.index,
|
||||
context,
|
||||
content: ast,
|
||||
// @ts-ignore
|
||||
attributes
|
||||
};
|
||||
}
|
||||
625
node_modules/svelte/src/compiler/phases/1-parse/read/style.js
generated
vendored
Normal file
625
node_modules/svelte/src/compiler/phases/1-parse/read/style.js
generated
vendored
Normal file
@@ -0,0 +1,625 @@
|
||||
/** @import { AST } from '#compiler' */
|
||||
/** @import { Parser } from '../index.js' */
|
||||
import * as e from '../../../errors.js';
|
||||
|
||||
const REGEX_MATCHER = /^[~^$*|]?=/;
|
||||
const REGEX_CLOSING_BRACKET = /[\s\]]/;
|
||||
const REGEX_ATTRIBUTE_FLAGS = /^[a-zA-Z]+/; // only `i` and `s` are valid today, but make it future-proof
|
||||
const REGEX_COMBINATOR = /^(\+|~|>|\|\|)/;
|
||||
const REGEX_PERCENTAGE = /^\d+(\.\d+)?%/;
|
||||
const REGEX_NTH_OF =
|
||||
/^(even|odd|\+?(\d+|\d*n(\s*[+-]\s*\d+)?)|-\d*n(\s*\+\s*\d+))((?=\s*[,)])|\s+of\s+)/;
|
||||
const REGEX_WHITESPACE_OR_COLON = /[\s:]/;
|
||||
const REGEX_LEADING_HYPHEN_OR_DIGIT = /-?\d/;
|
||||
const REGEX_VALID_IDENTIFIER_CHAR = /[a-zA-Z0-9_-]/;
|
||||
const REGEX_COMMENT_CLOSE = /\*\//;
|
||||
const REGEX_HTML_COMMENT_CLOSE = /-->/;
|
||||
|
||||
/**
|
||||
* @param {Parser} parser
|
||||
* @param {number} start
|
||||
* @param {Array<AST.Attribute | AST.SpreadAttribute | AST.Directive>} attributes
|
||||
* @returns {AST.CSS.StyleSheet}
|
||||
*/
|
||||
export default function read_style(parser, start, attributes) {
|
||||
const content_start = parser.index;
|
||||
const children = read_body(parser, '</style');
|
||||
const content_end = parser.index;
|
||||
|
||||
parser.read(/^<\/style\s*>/);
|
||||
|
||||
return {
|
||||
type: 'StyleSheet',
|
||||
start,
|
||||
end: parser.index,
|
||||
attributes,
|
||||
children,
|
||||
content: {
|
||||
start: content_start,
|
||||
end: content_end,
|
||||
styles: parser.template.slice(content_start, content_end),
|
||||
comment: null
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Parser} parser
|
||||
* @param {string} close
|
||||
* @returns {any[]}
|
||||
*/
|
||||
function read_body(parser, close) {
|
||||
/** @type {Array<AST.CSS.Rule | AST.CSS.Atrule>} */
|
||||
const children = [];
|
||||
|
||||
while (parser.index < parser.template.length) {
|
||||
allow_comment_or_whitespace(parser);
|
||||
|
||||
if (parser.match(close)) {
|
||||
return children;
|
||||
}
|
||||
|
||||
if (parser.match('@')) {
|
||||
children.push(read_at_rule(parser));
|
||||
} else {
|
||||
children.push(read_rule(parser));
|
||||
}
|
||||
}
|
||||
|
||||
e.expected_token(parser.template.length, close);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Parser} parser
|
||||
* @returns {AST.CSS.Atrule}
|
||||
*/
|
||||
function read_at_rule(parser) {
|
||||
const start = parser.index;
|
||||
parser.eat('@', true);
|
||||
|
||||
const name = read_identifier(parser);
|
||||
|
||||
const prelude = read_value(parser);
|
||||
|
||||
/** @type {AST.CSS.Block | null} */
|
||||
let block = null;
|
||||
|
||||
if (parser.match('{')) {
|
||||
// e.g. `@media (...) {...}`
|
||||
block = read_block(parser);
|
||||
} else {
|
||||
// e.g. `@import '...'`
|
||||
parser.eat(';', true);
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'Atrule',
|
||||
start,
|
||||
end: parser.index,
|
||||
name,
|
||||
prelude,
|
||||
block
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Parser} parser
|
||||
* @returns {AST.CSS.Rule}
|
||||
*/
|
||||
function read_rule(parser) {
|
||||
const start = parser.index;
|
||||
|
||||
return {
|
||||
type: 'Rule',
|
||||
prelude: read_selector_list(parser),
|
||||
block: read_block(parser),
|
||||
start,
|
||||
end: parser.index,
|
||||
metadata: {
|
||||
parent_rule: null,
|
||||
has_local_selectors: false,
|
||||
is_global_block: false
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Parser} parser
|
||||
* @param {boolean} [inside_pseudo_class]
|
||||
* @returns {AST.CSS.SelectorList}
|
||||
*/
|
||||
function read_selector_list(parser, inside_pseudo_class = false) {
|
||||
/** @type {AST.CSS.ComplexSelector[]} */
|
||||
const children = [];
|
||||
|
||||
allow_comment_or_whitespace(parser);
|
||||
|
||||
const start = parser.index;
|
||||
|
||||
while (parser.index < parser.template.length) {
|
||||
children.push(read_selector(parser, inside_pseudo_class));
|
||||
|
||||
const end = parser.index;
|
||||
|
||||
allow_comment_or_whitespace(parser);
|
||||
|
||||
if (inside_pseudo_class ? parser.match(')') : parser.match('{')) {
|
||||
return {
|
||||
type: 'SelectorList',
|
||||
start,
|
||||
end,
|
||||
children
|
||||
};
|
||||
} else {
|
||||
parser.eat(',', true);
|
||||
allow_comment_or_whitespace(parser);
|
||||
}
|
||||
}
|
||||
|
||||
e.unexpected_eof(parser.template.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Parser} parser
|
||||
* @param {boolean} [inside_pseudo_class]
|
||||
* @returns {AST.CSS.ComplexSelector}
|
||||
*/
|
||||
function read_selector(parser, inside_pseudo_class = false) {
|
||||
const list_start = parser.index;
|
||||
|
||||
/** @type {AST.CSS.RelativeSelector[]} */
|
||||
const children = [];
|
||||
|
||||
/**
|
||||
* @param {AST.CSS.Combinator | null} combinator
|
||||
* @param {number} start
|
||||
* @returns {AST.CSS.RelativeSelector}
|
||||
*/
|
||||
function create_selector(combinator, start) {
|
||||
return {
|
||||
type: 'RelativeSelector',
|
||||
combinator,
|
||||
selectors: [],
|
||||
start,
|
||||
end: -1,
|
||||
metadata: {
|
||||
is_global: false,
|
||||
is_global_like: false,
|
||||
scoped: false
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** @type {AST.CSS.RelativeSelector} */
|
||||
let relative_selector = create_selector(null, parser.index);
|
||||
|
||||
while (parser.index < parser.template.length) {
|
||||
let start = parser.index;
|
||||
|
||||
if (parser.eat('&')) {
|
||||
relative_selector.selectors.push({
|
||||
type: 'NestingSelector',
|
||||
name: '&',
|
||||
start,
|
||||
end: parser.index
|
||||
});
|
||||
} else if (parser.eat('*')) {
|
||||
let name = '*';
|
||||
|
||||
if (parser.eat('|')) {
|
||||
// * is the namespace (which we ignore)
|
||||
name = read_identifier(parser);
|
||||
}
|
||||
|
||||
relative_selector.selectors.push({
|
||||
type: 'TypeSelector',
|
||||
name,
|
||||
start,
|
||||
end: parser.index
|
||||
});
|
||||
} else if (parser.eat('#')) {
|
||||
relative_selector.selectors.push({
|
||||
type: 'IdSelector',
|
||||
name: read_identifier(parser),
|
||||
start,
|
||||
end: parser.index
|
||||
});
|
||||
} else if (parser.eat('.')) {
|
||||
relative_selector.selectors.push({
|
||||
type: 'ClassSelector',
|
||||
name: read_identifier(parser),
|
||||
start,
|
||||
end: parser.index
|
||||
});
|
||||
} else if (parser.eat('::')) {
|
||||
relative_selector.selectors.push({
|
||||
type: 'PseudoElementSelector',
|
||||
name: read_identifier(parser),
|
||||
start,
|
||||
end: parser.index
|
||||
});
|
||||
// We read the inner selectors of a pseudo element to ensure it parses correctly,
|
||||
// but we don't do anything with the result.
|
||||
if (parser.eat('(')) {
|
||||
read_selector_list(parser, true);
|
||||
parser.eat(')', true);
|
||||
}
|
||||
} else if (parser.eat(':')) {
|
||||
const name = read_identifier(parser);
|
||||
|
||||
/** @type {null | AST.CSS.SelectorList} */
|
||||
let args = null;
|
||||
|
||||
if (parser.eat('(')) {
|
||||
args = read_selector_list(parser, true);
|
||||
parser.eat(')', true);
|
||||
}
|
||||
|
||||
relative_selector.selectors.push({
|
||||
type: 'PseudoClassSelector',
|
||||
name,
|
||||
args,
|
||||
start,
|
||||
end: parser.index
|
||||
});
|
||||
} else if (parser.eat('[')) {
|
||||
parser.allow_whitespace();
|
||||
const name = read_identifier(parser);
|
||||
parser.allow_whitespace();
|
||||
|
||||
/** @type {string | null} */
|
||||
let value = null;
|
||||
|
||||
const matcher = parser.read(REGEX_MATCHER);
|
||||
|
||||
if (matcher) {
|
||||
parser.allow_whitespace();
|
||||
value = read_attribute_value(parser);
|
||||
}
|
||||
|
||||
parser.allow_whitespace();
|
||||
|
||||
const flags = parser.read(REGEX_ATTRIBUTE_FLAGS);
|
||||
|
||||
parser.allow_whitespace();
|
||||
parser.eat(']', true);
|
||||
|
||||
relative_selector.selectors.push({
|
||||
type: 'AttributeSelector',
|
||||
start,
|
||||
end: parser.index,
|
||||
name,
|
||||
matcher,
|
||||
value,
|
||||
flags
|
||||
});
|
||||
} else if (inside_pseudo_class && parser.match_regex(REGEX_NTH_OF)) {
|
||||
// nth of matcher must come before combinator matcher to prevent collision else the '+' in '+2n-1' would be parsed as a combinator
|
||||
|
||||
relative_selector.selectors.push({
|
||||
type: 'Nth',
|
||||
value: /**@type {string} */ (parser.read(REGEX_NTH_OF)),
|
||||
start,
|
||||
end: parser.index
|
||||
});
|
||||
} else if (parser.match_regex(REGEX_PERCENTAGE)) {
|
||||
relative_selector.selectors.push({
|
||||
type: 'Percentage',
|
||||
value: /** @type {string} */ (parser.read(REGEX_PERCENTAGE)),
|
||||
start,
|
||||
end: parser.index
|
||||
});
|
||||
} else if (!parser.match_regex(REGEX_COMBINATOR)) {
|
||||
let name = read_identifier(parser);
|
||||
|
||||
if (parser.eat('|')) {
|
||||
// we ignore the namespace when trying to find matching element classes
|
||||
name = read_identifier(parser);
|
||||
}
|
||||
|
||||
relative_selector.selectors.push({
|
||||
type: 'TypeSelector',
|
||||
name,
|
||||
start,
|
||||
end: parser.index
|
||||
});
|
||||
}
|
||||
|
||||
const index = parser.index;
|
||||
allow_comment_or_whitespace(parser);
|
||||
|
||||
if (parser.match(',') || (inside_pseudo_class ? parser.match(')') : parser.match('{'))) {
|
||||
// rewind, so we know whether to continue building the selector list
|
||||
parser.index = index;
|
||||
|
||||
relative_selector.end = index;
|
||||
children.push(relative_selector);
|
||||
|
||||
return {
|
||||
type: 'ComplexSelector',
|
||||
start: list_start,
|
||||
end: index,
|
||||
children,
|
||||
metadata: {
|
||||
rule: null,
|
||||
used: false
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
parser.index = index;
|
||||
const combinator = read_combinator(parser);
|
||||
|
||||
if (combinator) {
|
||||
if (relative_selector.selectors.length > 0) {
|
||||
relative_selector.end = index;
|
||||
children.push(relative_selector);
|
||||
}
|
||||
|
||||
// ...and start a new one
|
||||
relative_selector = create_selector(combinator, combinator.start);
|
||||
|
||||
parser.allow_whitespace();
|
||||
|
||||
if (parser.match(',') || (inside_pseudo_class ? parser.match(')') : parser.match('{'))) {
|
||||
e.css_selector_invalid(parser.index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
e.unexpected_eof(parser.template.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Parser} parser
|
||||
* @returns {AST.CSS.Combinator | null}
|
||||
*/
|
||||
function read_combinator(parser) {
|
||||
const start = parser.index;
|
||||
parser.allow_whitespace();
|
||||
|
||||
const index = parser.index;
|
||||
const name = parser.read(REGEX_COMBINATOR);
|
||||
|
||||
if (name) {
|
||||
const end = parser.index;
|
||||
parser.allow_whitespace();
|
||||
|
||||
return {
|
||||
type: 'Combinator',
|
||||
name,
|
||||
start: index,
|
||||
end
|
||||
};
|
||||
}
|
||||
|
||||
if (parser.index !== start) {
|
||||
return {
|
||||
type: 'Combinator',
|
||||
name: ' ',
|
||||
start,
|
||||
end: parser.index
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Parser} parser
|
||||
* @returns {AST.CSS.Block}
|
||||
*/
|
||||
function read_block(parser) {
|
||||
const start = parser.index;
|
||||
|
||||
parser.eat('{', true);
|
||||
|
||||
/** @type {Array<AST.CSS.Declaration | AST.CSS.Rule | AST.CSS.Atrule>} */
|
||||
const children = [];
|
||||
|
||||
while (parser.index < parser.template.length) {
|
||||
allow_comment_or_whitespace(parser);
|
||||
|
||||
if (parser.match('}')) {
|
||||
break;
|
||||
} else {
|
||||
children.push(read_block_item(parser));
|
||||
}
|
||||
}
|
||||
|
||||
parser.eat('}', true);
|
||||
|
||||
return {
|
||||
type: 'Block',
|
||||
start,
|
||||
end: parser.index,
|
||||
children
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a declaration, rule or at-rule
|
||||
*
|
||||
* @param {Parser} parser
|
||||
* @returns {AST.CSS.Declaration | AST.CSS.Rule | AST.CSS.Atrule}
|
||||
*/
|
||||
function read_block_item(parser) {
|
||||
if (parser.match('@')) {
|
||||
return read_at_rule(parser);
|
||||
}
|
||||
|
||||
// read ahead to understand whether we're dealing with a declaration or a nested rule.
|
||||
// this involves some duplicated work, but avoids a try-catch that would disguise errors
|
||||
const start = parser.index;
|
||||
read_value(parser);
|
||||
const char = parser.template[parser.index];
|
||||
parser.index = start;
|
||||
|
||||
return char === '{' ? read_rule(parser) : read_declaration(parser);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Parser} parser
|
||||
* @returns {AST.CSS.Declaration}
|
||||
*/
|
||||
function read_declaration(parser) {
|
||||
const start = parser.index;
|
||||
|
||||
const property = parser.read_until(REGEX_WHITESPACE_OR_COLON);
|
||||
parser.allow_whitespace();
|
||||
parser.eat(':');
|
||||
let index = parser.index;
|
||||
parser.allow_whitespace();
|
||||
|
||||
const value = read_value(parser);
|
||||
|
||||
if (!value && !property.startsWith('--')) {
|
||||
e.css_empty_declaration({ start, end: index });
|
||||
}
|
||||
|
||||
const end = parser.index;
|
||||
|
||||
if (!parser.match('}')) {
|
||||
parser.eat(';', true);
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'Declaration',
|
||||
start,
|
||||
end,
|
||||
property,
|
||||
value
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Parser} parser
|
||||
* @returns {string}
|
||||
*/
|
||||
function read_value(parser) {
|
||||
let value = '';
|
||||
let escaped = false;
|
||||
let in_url = false;
|
||||
|
||||
/** @type {null | '"' | "'"} */
|
||||
let quote_mark = null;
|
||||
|
||||
while (parser.index < parser.template.length) {
|
||||
const char = parser.template[parser.index];
|
||||
|
||||
if (escaped) {
|
||||
value += '\\' + char;
|
||||
escaped = false;
|
||||
} else if (char === '\\') {
|
||||
escaped = true;
|
||||
} else if (char === quote_mark) {
|
||||
quote_mark = null;
|
||||
} else if (char === ')') {
|
||||
in_url = false;
|
||||
} else if (quote_mark === null && (char === '"' || char === "'")) {
|
||||
quote_mark = char;
|
||||
} else if (char === '(' && value.slice(-3) === 'url') {
|
||||
in_url = true;
|
||||
} else if ((char === ';' || char === '{' || char === '}') && !in_url && !quote_mark) {
|
||||
return value.trim();
|
||||
}
|
||||
|
||||
value += char;
|
||||
|
||||
parser.index++;
|
||||
}
|
||||
|
||||
e.unexpected_eof(parser.template.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a property that may or may not be quoted, e.g.
|
||||
* `foo` or `'foo bar'` or `"foo bar"`
|
||||
* @param {Parser} parser
|
||||
*/
|
||||
function read_attribute_value(parser) {
|
||||
let value = '';
|
||||
let escaped = false;
|
||||
const quote_mark = parser.eat('"') ? '"' : parser.eat("'") ? "'" : null;
|
||||
|
||||
while (parser.index < parser.template.length) {
|
||||
const char = parser.template[parser.index];
|
||||
if (escaped) {
|
||||
value += '\\' + char;
|
||||
escaped = false;
|
||||
} else if (char === '\\') {
|
||||
escaped = true;
|
||||
} else if (quote_mark ? char === quote_mark : REGEX_CLOSING_BRACKET.test(char)) {
|
||||
if (quote_mark) {
|
||||
parser.eat(quote_mark, true);
|
||||
}
|
||||
|
||||
return value.trim();
|
||||
} else {
|
||||
value += char;
|
||||
}
|
||||
|
||||
parser.index++;
|
||||
}
|
||||
|
||||
e.unexpected_eof(parser.template.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* https://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
|
||||
* @param {Parser} parser
|
||||
*/
|
||||
function read_identifier(parser) {
|
||||
const start = parser.index;
|
||||
|
||||
let identifier = '';
|
||||
|
||||
if (parser.match('--') || parser.match_regex(REGEX_LEADING_HYPHEN_OR_DIGIT)) {
|
||||
e.css_expected_identifier(start);
|
||||
}
|
||||
|
||||
let escaped = false;
|
||||
|
||||
while (parser.index < parser.template.length) {
|
||||
const char = parser.template[parser.index];
|
||||
if (escaped) {
|
||||
identifier += '\\' + char;
|
||||
escaped = false;
|
||||
} else if (char === '\\') {
|
||||
escaped = true;
|
||||
} else if (
|
||||
/** @type {number} */ (char.codePointAt(0)) >= 160 ||
|
||||
REGEX_VALID_IDENTIFIER_CHAR.test(char)
|
||||
) {
|
||||
identifier += char;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
parser.index++;
|
||||
}
|
||||
|
||||
if (identifier === '') {
|
||||
e.css_expected_identifier(start);
|
||||
}
|
||||
|
||||
return identifier;
|
||||
}
|
||||
|
||||
/** @param {Parser} parser */
|
||||
function allow_comment_or_whitespace(parser) {
|
||||
parser.allow_whitespace();
|
||||
while (parser.match('/*') || parser.match('<!--')) {
|
||||
if (parser.eat('/*')) {
|
||||
parser.read_until(REGEX_COMMENT_CLOSE);
|
||||
parser.eat('*/', true);
|
||||
}
|
||||
|
||||
if (parser.eat('<!--')) {
|
||||
parser.read_until(REGEX_HTML_COMMENT_CLOSE);
|
||||
parser.eat('-->', true);
|
||||
}
|
||||
|
||||
parser.allow_whitespace();
|
||||
}
|
||||
}
|
||||
147
node_modules/svelte/src/compiler/phases/1-parse/remove_typescript_nodes.js
generated
vendored
Normal file
147
node_modules/svelte/src/compiler/phases/1-parse/remove_typescript_nodes.js
generated
vendored
Normal file
@@ -0,0 +1,147 @@
|
||||
/** @import { Context, Visitors } from 'zimmerframe' */
|
||||
/** @import { FunctionExpression, FunctionDeclaration } from 'estree' */
|
||||
import { walk } from 'zimmerframe';
|
||||
import * as b from '../../utils/builders.js';
|
||||
import * as e from '../../errors.js';
|
||||
|
||||
/**
|
||||
* @param {FunctionExpression | FunctionDeclaration} node
|
||||
* @param {Context<any, any>} context
|
||||
*/
|
||||
function remove_this_param(node, context) {
|
||||
if (node.params[0]?.type === 'Identifier' && node.params[0].name === 'this') {
|
||||
node.params.shift();
|
||||
}
|
||||
return context.next();
|
||||
}
|
||||
|
||||
/** @type {Visitors<any, null>} */
|
||||
const visitors = {
|
||||
_(node, context) {
|
||||
const n = context.next() ?? node;
|
||||
|
||||
// TODO there may come a time when we decide to preserve type annotations.
|
||||
// until that day comes, we just delete them so they don't confuse esrap
|
||||
delete n.typeAnnotation;
|
||||
delete n.typeParameters;
|
||||
delete n.returnType;
|
||||
delete n.accessibility;
|
||||
},
|
||||
Decorator(node) {
|
||||
e.typescript_invalid_feature(node, 'decorators (related TSC proposal is not stage 4 yet)');
|
||||
},
|
||||
ImportDeclaration(node) {
|
||||
if (node.importKind === 'type') return b.empty;
|
||||
|
||||
if (node.specifiers?.length > 0) {
|
||||
const specifiers = node.specifiers.filter((/** @type {any} */ s) => s.importKind !== 'type');
|
||||
if (specifiers.length === 0) return b.empty;
|
||||
|
||||
return { ...node, specifiers };
|
||||
}
|
||||
|
||||
return node;
|
||||
},
|
||||
ExportNamedDeclaration(node, context) {
|
||||
if (node.exportKind === 'type') return b.empty;
|
||||
|
||||
if (node.declaration) {
|
||||
const result = context.next();
|
||||
if (result?.declaration?.type === 'EmptyStatement') {
|
||||
return b.empty;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
if (node.specifiers) {
|
||||
const specifiers = node.specifiers.filter((/** @type {any} */ s) => s.exportKind !== 'type');
|
||||
if (specifiers.length === 0) return b.empty;
|
||||
|
||||
return { ...node, specifiers };
|
||||
}
|
||||
|
||||
return node;
|
||||
},
|
||||
ExportDefaultDeclaration(node) {
|
||||
if (node.exportKind === 'type') return b.empty;
|
||||
return node;
|
||||
},
|
||||
ExportAllDeclaration(node) {
|
||||
if (node.exportKind === 'type') return b.empty;
|
||||
return node;
|
||||
},
|
||||
PropertyDefinition(node, { next }) {
|
||||
if (node.accessor) {
|
||||
e.typescript_invalid_feature(
|
||||
node,
|
||||
'accessor fields (related TSC proposal is not stage 4 yet)'
|
||||
);
|
||||
}
|
||||
return next();
|
||||
},
|
||||
TSAsExpression(node, context) {
|
||||
return context.visit(node.expression);
|
||||
},
|
||||
TSSatisfiesExpression(node, context) {
|
||||
return context.visit(node.expression);
|
||||
},
|
||||
TSNonNullExpression(node, context) {
|
||||
return context.visit(node.expression);
|
||||
},
|
||||
TSInterfaceDeclaration() {
|
||||
return b.empty;
|
||||
},
|
||||
TSTypeAliasDeclaration() {
|
||||
return b.empty;
|
||||
},
|
||||
TSEnumDeclaration(node) {
|
||||
e.typescript_invalid_feature(node, 'enums');
|
||||
},
|
||||
TSParameterProperty(node, context) {
|
||||
if ((node.readonly || node.accessibility) && context.path.at(-2)?.kind === 'constructor') {
|
||||
e.typescript_invalid_feature(node, 'accessibility modifiers on constructor parameters');
|
||||
}
|
||||
return context.visit(node.parameter);
|
||||
},
|
||||
TSInstantiationExpression(node, context) {
|
||||
return context.visit(node.expression);
|
||||
},
|
||||
FunctionExpression: remove_this_param,
|
||||
FunctionDeclaration: remove_this_param,
|
||||
TSDeclareFunction() {
|
||||
return b.empty;
|
||||
},
|
||||
ClassDeclaration(node, context) {
|
||||
if (node.declare) {
|
||||
return b.empty;
|
||||
}
|
||||
delete node.implements;
|
||||
return context.next();
|
||||
},
|
||||
VariableDeclaration(node, context) {
|
||||
if (node.declare) {
|
||||
return b.empty;
|
||||
}
|
||||
return context.next();
|
||||
},
|
||||
TSModuleDeclaration(node, context) {
|
||||
if (!node.body) return b.empty;
|
||||
|
||||
// namespaces can contain non-type nodes
|
||||
const cleaned = /** @type {any[]} */ (node.body.body).map((entry) => context.visit(entry));
|
||||
if (cleaned.some((entry) => entry !== b.empty)) {
|
||||
e.typescript_invalid_feature(node, 'namespaces with non-type nodes');
|
||||
}
|
||||
|
||||
return b.empty;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param {T} ast
|
||||
* @returns {T}
|
||||
*/
|
||||
export function remove_typescript_nodes(ast) {
|
||||
return walk(ast, null, visitors);
|
||||
}
|
||||
823
node_modules/svelte/src/compiler/phases/1-parse/state/element.js
generated
vendored
Normal file
823
node_modules/svelte/src/compiler/phases/1-parse/state/element.js
generated
vendored
Normal file
@@ -0,0 +1,823 @@
|
||||
/** @import { Expression } from 'estree' */
|
||||
/** @import { AST } from '#compiler' */
|
||||
/** @import { Parser } from '../index.js' */
|
||||
import { is_void } from '../../../../utils.js';
|
||||
import read_expression from '../read/expression.js';
|
||||
import { read_script } from '../read/script.js';
|
||||
import read_style from '../read/style.js';
|
||||
import { decode_character_references } from '../utils/html.js';
|
||||
import * as e from '../../../errors.js';
|
||||
import * as w from '../../../warnings.js';
|
||||
import { create_fragment } from '../utils/create.js';
|
||||
import { create_attribute, create_expression_metadata, is_element_node } from '../../nodes.js';
|
||||
import { get_attribute_expression, is_expression_attribute } from '../../../utils/ast.js';
|
||||
import { closing_tag_omitted } from '../../../../html-tree-validation.js';
|
||||
import { list } from '../../../utils/string.js';
|
||||
import { regex_whitespace } from '../../patterns.js';
|
||||
|
||||
const regex_invalid_unquoted_attribute_value = /^(\/>|[\s"'=<>`])/;
|
||||
const regex_closing_textarea_tag = /^<\/textarea(\s[^>]*)?>/i;
|
||||
const regex_closing_comment = /-->/;
|
||||
const regex_whitespace_or_slash_or_closing_tag = /(\s|\/|>)/;
|
||||
const regex_token_ending_character = /[\s=/>"']/;
|
||||
const regex_starts_with_quote_characters = /^["']/;
|
||||
const regex_attribute_value = /^(?:"([^"]*)"|'([^'])*'|([^>\s]+))/;
|
||||
const regex_valid_element_name =
|
||||
/^(?:![a-zA-Z]+|[a-zA-Z](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?|[a-zA-Z][a-zA-Z0-9]*:[a-zA-Z][a-zA-Z0-9-]*[a-zA-Z0-9])$/;
|
||||
export const regex_valid_component_name =
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#identifiers adjusted for our needs
|
||||
// (must start with uppercase letter if no dots, can contain dots)
|
||||
/^(?:\p{Lu}[$\u200c\u200d\p{ID_Continue}.]*|\p{ID_Start}[$\u200c\u200d\p{ID_Continue}]*(?:\.[$\u200c\u200d\p{ID_Continue}]+)+)$/u;
|
||||
|
||||
/** @type {Map<string, AST.ElementLike['type']>} */
|
||||
const root_only_meta_tags = new Map([
|
||||
['svelte:head', 'SvelteHead'],
|
||||
['svelte:options', 'SvelteOptions'],
|
||||
['svelte:window', 'SvelteWindow'],
|
||||
['svelte:document', 'SvelteDocument'],
|
||||
['svelte:body', 'SvelteBody']
|
||||
]);
|
||||
|
||||
/** @type {Map<string, AST.ElementLike['type']>} */
|
||||
const meta_tags = new Map([
|
||||
...root_only_meta_tags,
|
||||
['svelte:element', 'SvelteElement'],
|
||||
['svelte:component', 'SvelteComponent'],
|
||||
['svelte:self', 'SvelteSelf'],
|
||||
['svelte:fragment', 'SvelteFragment'],
|
||||
['svelte:boundary', 'SvelteBoundary']
|
||||
]);
|
||||
|
||||
/** @param {Parser} parser */
|
||||
export default function element(parser) {
|
||||
const start = parser.index++;
|
||||
|
||||
let parent = parser.current();
|
||||
|
||||
if (parser.eat('!--')) {
|
||||
const data = parser.read_until(regex_closing_comment);
|
||||
parser.eat('-->', true);
|
||||
|
||||
parser.append({
|
||||
type: 'Comment',
|
||||
start,
|
||||
end: parser.index,
|
||||
data
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const is_closing_tag = parser.eat('/');
|
||||
const name = parser.read_until(regex_whitespace_or_slash_or_closing_tag);
|
||||
|
||||
if (is_closing_tag) {
|
||||
parser.allow_whitespace();
|
||||
parser.eat('>', true);
|
||||
|
||||
if (is_void(name)) {
|
||||
e.void_element_invalid_content(start);
|
||||
}
|
||||
|
||||
// close any elements that don't have their own closing tags, e.g. <div><p></div>
|
||||
while (/** @type {AST.RegularElement} */ (parent).name !== name) {
|
||||
if (parser.loose) {
|
||||
// If the previous element did interpret the next opening tag as an attribute, backtrack
|
||||
if (is_element_node(parent)) {
|
||||
const last = parent.attributes.at(-1);
|
||||
if (last?.type === 'Attribute' && last.name === `<${name}`) {
|
||||
parser.index = last.start;
|
||||
parent.attributes.pop();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (parent.type !== 'RegularElement' && !parser.loose) {
|
||||
if (parser.last_auto_closed_tag && parser.last_auto_closed_tag.tag === name) {
|
||||
e.element_invalid_closing_tag_autoclosed(start, name, parser.last_auto_closed_tag.reason);
|
||||
} else {
|
||||
e.element_invalid_closing_tag(start, name);
|
||||
}
|
||||
}
|
||||
|
||||
parent.end = start;
|
||||
parser.pop();
|
||||
|
||||
parent = parser.current();
|
||||
}
|
||||
|
||||
parent.end = parser.index;
|
||||
parser.pop();
|
||||
|
||||
if (parser.last_auto_closed_tag && parser.stack.length < parser.last_auto_closed_tag.depth) {
|
||||
parser.last_auto_closed_tag = undefined;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (name.startsWith('svelte:') && !meta_tags.has(name)) {
|
||||
const bounds = { start: start + 1, end: start + 1 + name.length };
|
||||
e.svelte_meta_invalid_tag(bounds, list(Array.from(meta_tags.keys())));
|
||||
}
|
||||
|
||||
if (!regex_valid_element_name.test(name) && !regex_valid_component_name.test(name)) {
|
||||
// <div. -> in the middle of typing -> allow in loose mode
|
||||
if (!parser.loose || !name.endsWith('.')) {
|
||||
const bounds = { start: start + 1, end: start + 1 + name.length };
|
||||
e.tag_invalid_name(bounds);
|
||||
}
|
||||
}
|
||||
|
||||
if (root_only_meta_tags.has(name)) {
|
||||
if (name in parser.meta_tags) {
|
||||
e.svelte_meta_duplicate(start, name);
|
||||
}
|
||||
|
||||
if (parent.type !== 'Root') {
|
||||
e.svelte_meta_invalid_placement(start, name);
|
||||
}
|
||||
|
||||
parser.meta_tags[name] = true;
|
||||
}
|
||||
|
||||
const type = meta_tags.has(name)
|
||||
? meta_tags.get(name)
|
||||
: regex_valid_component_name.test(name) || (parser.loose && name.endsWith('.'))
|
||||
? 'Component'
|
||||
: name === 'title' && parent_is_head(parser.stack)
|
||||
? 'TitleElement'
|
||||
: // TODO Svelte 6/7: once slots are removed in favor of snippets, always keep slot as a regular element
|
||||
name === 'slot' && !parent_is_shadowroot_template(parser.stack)
|
||||
? 'SlotElement'
|
||||
: 'RegularElement';
|
||||
|
||||
/** @type {AST.ElementLike} */
|
||||
const element =
|
||||
type === 'RegularElement'
|
||||
? {
|
||||
type,
|
||||
start,
|
||||
end: -1,
|
||||
name,
|
||||
attributes: [],
|
||||
fragment: create_fragment(true),
|
||||
metadata: {
|
||||
svg: false,
|
||||
mathml: false,
|
||||
scoped: false,
|
||||
has_spread: false,
|
||||
path: []
|
||||
}
|
||||
}
|
||||
: /** @type {AST.ElementLike} */ ({
|
||||
type,
|
||||
start,
|
||||
end: -1,
|
||||
name,
|
||||
attributes: [],
|
||||
fragment: create_fragment(true),
|
||||
metadata: {
|
||||
// unpopulated at first, differs between types
|
||||
}
|
||||
});
|
||||
|
||||
parser.allow_whitespace();
|
||||
|
||||
if (parent.type === 'RegularElement' && closing_tag_omitted(parent.name, name)) {
|
||||
parent.end = start;
|
||||
parser.pop();
|
||||
parser.last_auto_closed_tag = {
|
||||
tag: parent.name,
|
||||
reason: name,
|
||||
depth: parser.stack.length
|
||||
};
|
||||
}
|
||||
|
||||
/** @type {string[]} */
|
||||
const unique_names = [];
|
||||
|
||||
const current = parser.current();
|
||||
const is_top_level_script_or_style =
|
||||
(name === 'script' || name === 'style') && current.type === 'Root';
|
||||
|
||||
const read = is_top_level_script_or_style ? read_static_attribute : read_attribute;
|
||||
|
||||
let attribute;
|
||||
while ((attribute = read(parser))) {
|
||||
// animate and transition can only be specified once per element so no need
|
||||
// to check here, use can be used multiple times, same for the on directive
|
||||
// finally let already has error handling in case of duplicate variable names
|
||||
if (
|
||||
attribute.type === 'Attribute' ||
|
||||
attribute.type === 'BindDirective' ||
|
||||
attribute.type === 'StyleDirective' ||
|
||||
attribute.type === 'ClassDirective'
|
||||
) {
|
||||
// `bind:attribute` and `attribute` are just the same but `class:attribute`,
|
||||
// `style:attribute` and `attribute` are different and should be allowed together
|
||||
// so we concatenate the type while normalizing the type for BindDirective
|
||||
const type = attribute.type === 'BindDirective' ? 'Attribute' : attribute.type;
|
||||
if (unique_names.includes(type + attribute.name)) {
|
||||
e.attribute_duplicate(attribute);
|
||||
// <svelte:element bind:this this=..> is allowed
|
||||
} else if (attribute.name !== 'this') {
|
||||
unique_names.push(type + attribute.name);
|
||||
}
|
||||
}
|
||||
|
||||
element.attributes.push(attribute);
|
||||
parser.allow_whitespace();
|
||||
}
|
||||
|
||||
if (element.type === 'SvelteComponent') {
|
||||
const index = element.attributes.findIndex(
|
||||
/** @param {any} attr */
|
||||
(attr) => attr.type === 'Attribute' && attr.name === 'this'
|
||||
);
|
||||
if (index === -1) {
|
||||
e.svelte_component_missing_this(start);
|
||||
}
|
||||
|
||||
const definition = /** @type {AST.Attribute} */ (element.attributes.splice(index, 1)[0]);
|
||||
if (!is_expression_attribute(definition)) {
|
||||
e.svelte_component_invalid_this(definition.start);
|
||||
}
|
||||
|
||||
element.expression = get_attribute_expression(definition);
|
||||
}
|
||||
|
||||
if (element.type === 'SvelteElement') {
|
||||
const index = element.attributes.findIndex(
|
||||
/** @param {any} attr */
|
||||
(attr) => attr.type === 'Attribute' && attr.name === 'this'
|
||||
);
|
||||
if (index === -1) {
|
||||
e.svelte_element_missing_this(start);
|
||||
}
|
||||
|
||||
const definition = /** @type {AST.Attribute} */ (element.attributes.splice(index, 1)[0]);
|
||||
|
||||
if (definition.value === true) {
|
||||
e.svelte_element_missing_this(definition);
|
||||
}
|
||||
|
||||
if (!is_expression_attribute(definition)) {
|
||||
w.svelte_element_invalid_this(definition);
|
||||
|
||||
// note that this is wrong, in the case of e.g. `this="h{n}"` — it will result in `<h>`.
|
||||
// it would be much better to just error here, but we are preserving the existing buggy
|
||||
// Svelte 4 behaviour out of an overabundance of caution regarding breaking changes.
|
||||
// TODO in 6.0, error
|
||||
const chunk = /** @type {Array<AST.ExpressionTag | AST.Text>} */ (definition.value)[0];
|
||||
element.tag =
|
||||
chunk.type === 'Text'
|
||||
? {
|
||||
type: 'Literal',
|
||||
value: chunk.data,
|
||||
raw: `'${chunk.raw}'`,
|
||||
start: chunk.start,
|
||||
end: chunk.end
|
||||
}
|
||||
: chunk.expression;
|
||||
} else {
|
||||
element.tag = get_attribute_expression(definition);
|
||||
}
|
||||
}
|
||||
|
||||
if (is_top_level_script_or_style) {
|
||||
parser.eat('>', true);
|
||||
|
||||
/** @type {AST.Comment | null} */
|
||||
let prev_comment = null;
|
||||
for (let i = current.fragment.nodes.length - 1; i >= 0; i--) {
|
||||
const node = current.fragment.nodes[i];
|
||||
|
||||
if (i === current.fragment.nodes.length - 1 && node.end !== start) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (node.type === 'Comment') {
|
||||
prev_comment = node;
|
||||
break;
|
||||
} else if (node.type !== 'Text' || node.data.trim()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (name === 'script') {
|
||||
const content = read_script(parser, start, element.attributes);
|
||||
if (prev_comment) {
|
||||
// We take advantage of the fact that the root will never have leadingComments set,
|
||||
// and set the previous comment to it so that the warning mechanism can later
|
||||
// inspect the root and see if there was a html comment before it silencing specific warnings.
|
||||
content.content.leadingComments = [{ type: 'Line', value: prev_comment.data }];
|
||||
}
|
||||
|
||||
if (content.context === 'module') {
|
||||
if (current.module) e.script_duplicate(start);
|
||||
current.module = content;
|
||||
} else {
|
||||
if (current.instance) e.script_duplicate(start);
|
||||
current.instance = content;
|
||||
}
|
||||
} else {
|
||||
const content = read_style(parser, start, element.attributes);
|
||||
content.content.comment = prev_comment;
|
||||
|
||||
if (current.css) e.style_duplicate(start);
|
||||
current.css = content;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
parser.append(element);
|
||||
|
||||
const self_closing = parser.eat('/') || is_void(name);
|
||||
const closed = parser.eat('>', true, false);
|
||||
|
||||
// Loose parsing mode
|
||||
if (!closed) {
|
||||
// We may have eaten an opening `<` of the next element and treated it as an attribute...
|
||||
const last = element.attributes.at(-1);
|
||||
if (last?.type === 'Attribute' && last.name === '<') {
|
||||
parser.index = last.start;
|
||||
element.attributes.pop();
|
||||
} else {
|
||||
// ... or we may have eaten part of a following block ...
|
||||
const prev_1 = parser.template[parser.index - 1];
|
||||
const prev_2 = parser.template[parser.index - 2];
|
||||
const current = parser.template[parser.index];
|
||||
if (prev_2 === '{' && prev_1 === '/') {
|
||||
parser.index -= 2;
|
||||
} else if (prev_1 === '{' && (current === '#' || current === '@' || current === ':')) {
|
||||
parser.index -= 1;
|
||||
} else {
|
||||
// ... or we're followed by whitespace, for example near the end of the template,
|
||||
// which we want to take in so that language tools has more room to work with
|
||||
parser.allow_whitespace();
|
||||
if (parser.index === parser.template.length) {
|
||||
while (
|
||||
parser.index < parser.template_untrimmed.length &&
|
||||
regex_whitespace.test(parser.template_untrimmed[parser.index])
|
||||
) {
|
||||
parser.index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (self_closing || !closed) {
|
||||
// don't push self-closing elements onto the stack
|
||||
element.end = parser.index;
|
||||
} else if (name === 'textarea') {
|
||||
// special case
|
||||
element.fragment.nodes = read_sequence(
|
||||
parser,
|
||||
() => regex_closing_textarea_tag.test(parser.template.slice(parser.index)),
|
||||
'inside <textarea>'
|
||||
);
|
||||
parser.read(regex_closing_textarea_tag);
|
||||
element.end = parser.index;
|
||||
} else if (name === 'script' || name === 'style') {
|
||||
// special case
|
||||
const start = parser.index;
|
||||
const data = parser.read_until(new RegExp(`</${name}>`));
|
||||
const end = parser.index;
|
||||
|
||||
/** @type {AST.Text} */
|
||||
const node = {
|
||||
start,
|
||||
end,
|
||||
type: 'Text',
|
||||
data,
|
||||
raw: data
|
||||
};
|
||||
|
||||
element.fragment.nodes.push(node);
|
||||
parser.eat(`</${name}>`, true);
|
||||
element.end = parser.index;
|
||||
} else {
|
||||
parser.stack.push(element);
|
||||
parser.fragments.push(element.fragment);
|
||||
}
|
||||
}
|
||||
|
||||
/** @param {AST.TemplateNode[]} stack */
|
||||
function parent_is_head(stack) {
|
||||
let i = stack.length;
|
||||
while (i--) {
|
||||
const { type } = stack[i];
|
||||
if (type === 'SvelteHead') return true;
|
||||
if (type === 'RegularElement' || type === 'Component') return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @param {AST.TemplateNode[]} stack */
|
||||
function parent_is_shadowroot_template(stack) {
|
||||
// https://developer.chrome.com/docs/css-ui/declarative-shadow-dom#building_a_declarative_shadow_root
|
||||
let i = stack.length;
|
||||
while (i--) {
|
||||
if (
|
||||
stack[i].type === 'RegularElement' &&
|
||||
/** @type {AST.RegularElement} */ (stack[i]).attributes.some(
|
||||
(a) => a.type === 'Attribute' && a.name === 'shadowrootmode'
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Parser} parser
|
||||
* @returns {AST.Attribute | null}
|
||||
*/
|
||||
function read_static_attribute(parser) {
|
||||
const start = parser.index;
|
||||
|
||||
const name = parser.read_until(regex_token_ending_character);
|
||||
if (!name) return null;
|
||||
|
||||
/** @type {true | Array<AST.Text | AST.ExpressionTag>} */
|
||||
let value = true;
|
||||
|
||||
if (parser.eat('=')) {
|
||||
parser.allow_whitespace();
|
||||
let raw = parser.match_regex(regex_attribute_value);
|
||||
if (!raw) {
|
||||
e.expected_attribute_value(parser.index);
|
||||
}
|
||||
|
||||
parser.index += raw.length;
|
||||
|
||||
const quoted = raw[0] === '"' || raw[0] === "'";
|
||||
if (quoted) {
|
||||
raw = raw.slice(1, -1);
|
||||
}
|
||||
|
||||
value = [
|
||||
{
|
||||
start: parser.index - raw.length - (quoted ? 1 : 0),
|
||||
end: quoted ? parser.index - 1 : parser.index,
|
||||
type: 'Text',
|
||||
raw: raw,
|
||||
data: decode_character_references(raw, true)
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
if (parser.match_regex(regex_starts_with_quote_characters)) {
|
||||
e.expected_token(parser.index, '=');
|
||||
}
|
||||
|
||||
return create_attribute(name, start, parser.index, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Parser} parser
|
||||
* @returns {AST.Attribute | AST.SpreadAttribute | AST.Directive | null}
|
||||
*/
|
||||
function read_attribute(parser) {
|
||||
const start = parser.index;
|
||||
|
||||
if (parser.eat('{')) {
|
||||
parser.allow_whitespace();
|
||||
|
||||
if (parser.eat('...')) {
|
||||
const expression = read_expression(parser);
|
||||
|
||||
parser.allow_whitespace();
|
||||
parser.eat('}', true);
|
||||
|
||||
/** @type {AST.SpreadAttribute} */
|
||||
const spread = {
|
||||
type: 'SpreadAttribute',
|
||||
start,
|
||||
end: parser.index,
|
||||
expression,
|
||||
metadata: {
|
||||
expression: create_expression_metadata()
|
||||
}
|
||||
};
|
||||
|
||||
return spread;
|
||||
} else {
|
||||
const value_start = parser.index;
|
||||
let name = parser.read_identifier();
|
||||
|
||||
if (name === null) {
|
||||
if (
|
||||
parser.loose &&
|
||||
(parser.match('#') || parser.match('/') || parser.match('@') || parser.match(':'))
|
||||
) {
|
||||
// We're likely in an unclosed opening tag and did read part of a block.
|
||||
// Return null to not crash the parser so it can continue with closing the tag.
|
||||
return null;
|
||||
} else if (parser.loose && parser.match('}')) {
|
||||
// Likely in the middle of typing, just created the shorthand
|
||||
name = '';
|
||||
} else {
|
||||
e.attribute_empty_shorthand(start);
|
||||
}
|
||||
}
|
||||
|
||||
parser.allow_whitespace();
|
||||
parser.eat('}', true);
|
||||
|
||||
/** @type {AST.ExpressionTag} */
|
||||
const expression = {
|
||||
type: 'ExpressionTag',
|
||||
start: value_start,
|
||||
end: value_start + name.length,
|
||||
expression: {
|
||||
start: value_start,
|
||||
end: value_start + name.length,
|
||||
type: 'Identifier',
|
||||
name
|
||||
},
|
||||
metadata: {
|
||||
expression: create_expression_metadata()
|
||||
}
|
||||
};
|
||||
|
||||
return create_attribute(name, start, parser.index, expression);
|
||||
}
|
||||
}
|
||||
|
||||
const name = parser.read_until(regex_token_ending_character);
|
||||
if (!name) return null;
|
||||
|
||||
let end = parser.index;
|
||||
|
||||
parser.allow_whitespace();
|
||||
|
||||
const colon_index = name.indexOf(':');
|
||||
const type = colon_index !== -1 && get_directive_type(name.slice(0, colon_index));
|
||||
|
||||
/** @type {true | AST.ExpressionTag | Array<AST.Text | AST.ExpressionTag>} */
|
||||
let value = true;
|
||||
if (parser.eat('=')) {
|
||||
parser.allow_whitespace();
|
||||
|
||||
if (parser.template[parser.index] === '/' && parser.template[parser.index + 1] === '>') {
|
||||
const char_start = parser.index;
|
||||
parser.index++; // consume '/'
|
||||
value = [
|
||||
{
|
||||
start: char_start,
|
||||
end: char_start + 1,
|
||||
type: 'Text',
|
||||
raw: '/',
|
||||
data: '/'
|
||||
}
|
||||
];
|
||||
end = parser.index;
|
||||
} else {
|
||||
value = read_attribute_value(parser);
|
||||
end = parser.index;
|
||||
}
|
||||
} else if (parser.match_regex(regex_starts_with_quote_characters)) {
|
||||
e.expected_token(parser.index, '=');
|
||||
}
|
||||
|
||||
if (type) {
|
||||
const [directive_name, ...modifiers] = name.slice(colon_index + 1).split('|');
|
||||
|
||||
if (directive_name === '') {
|
||||
e.directive_missing_name({ start, end: start + colon_index + 1 }, name);
|
||||
}
|
||||
|
||||
if (type === 'StyleDirective') {
|
||||
return {
|
||||
start,
|
||||
end,
|
||||
type,
|
||||
name: directive_name,
|
||||
modifiers: /** @type {Array<'important'>} */ (modifiers),
|
||||
value,
|
||||
metadata: {
|
||||
expression: create_expression_metadata()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const first_value = value === true ? undefined : Array.isArray(value) ? value[0] : value;
|
||||
|
||||
/** @type {Expression | null} */
|
||||
let expression = null;
|
||||
|
||||
if (first_value) {
|
||||
const attribute_contains_text =
|
||||
/** @type {any[]} */ (value).length > 1 || first_value.type === 'Text';
|
||||
if (attribute_contains_text) {
|
||||
e.directive_invalid_value(/** @type {number} */ (first_value.start));
|
||||
} else {
|
||||
// TODO throw a parser error in a future version here if this `[ExpressionTag]` instead of `ExpressionTag`,
|
||||
// which means stringified value, which isn't allowed for some directives?
|
||||
expression = first_value.expression;
|
||||
}
|
||||
}
|
||||
|
||||
/** @type {AST.Directive} */
|
||||
const directive = {
|
||||
start,
|
||||
end,
|
||||
type,
|
||||
name: directive_name,
|
||||
expression,
|
||||
metadata: {
|
||||
expression: create_expression_metadata()
|
||||
}
|
||||
};
|
||||
|
||||
// @ts-expect-error we do this separately from the declaration to avoid upsetting typescript
|
||||
directive.modifiers = modifiers;
|
||||
|
||||
if (directive.type === 'TransitionDirective') {
|
||||
const direction = name.slice(0, colon_index);
|
||||
directive.intro = direction === 'in' || direction === 'transition';
|
||||
directive.outro = direction === 'out' || direction === 'transition';
|
||||
}
|
||||
|
||||
// Directive name is expression, e.g. <p class:isRed />
|
||||
if (
|
||||
(directive.type === 'BindDirective' || directive.type === 'ClassDirective') &&
|
||||
!directive.expression
|
||||
) {
|
||||
directive.expression = /** @type {any} */ ({
|
||||
start: start + colon_index + 1,
|
||||
end,
|
||||
type: 'Identifier',
|
||||
name: directive.name
|
||||
});
|
||||
}
|
||||
|
||||
return directive;
|
||||
}
|
||||
|
||||
return create_attribute(name, start, end, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @returns {any}
|
||||
*/
|
||||
function get_directive_type(name) {
|
||||
if (name === 'use') return 'UseDirective';
|
||||
if (name === 'animate') return 'AnimateDirective';
|
||||
if (name === 'bind') return 'BindDirective';
|
||||
if (name === 'class') return 'ClassDirective';
|
||||
if (name === 'style') return 'StyleDirective';
|
||||
if (name === 'on') return 'OnDirective';
|
||||
if (name === 'let') return 'LetDirective';
|
||||
if (name === 'in' || name === 'out' || name === 'transition') return 'TransitionDirective';
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Parser} parser
|
||||
* @return {AST.ExpressionTag | Array<AST.ExpressionTag | AST.Text>}
|
||||
*/
|
||||
function read_attribute_value(parser) {
|
||||
const quote_mark = parser.eat("'") ? "'" : parser.eat('"') ? '"' : null;
|
||||
if (quote_mark && parser.eat(quote_mark)) {
|
||||
return [
|
||||
{
|
||||
start: parser.index - 1,
|
||||
end: parser.index - 1,
|
||||
type: 'Text',
|
||||
raw: '',
|
||||
data: ''
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/** @type {Array<AST.ExpressionTag | AST.Text>} */
|
||||
let value;
|
||||
try {
|
||||
value = read_sequence(
|
||||
parser,
|
||||
() => {
|
||||
// handle common case of quote marks existing outside of regex for performance reasons
|
||||
if (quote_mark) return parser.match(quote_mark);
|
||||
return !!parser.match_regex(regex_invalid_unquoted_attribute_value);
|
||||
},
|
||||
'in attribute value'
|
||||
);
|
||||
} catch (/** @type {any} */ error) {
|
||||
if (error.code === 'js_parse_error') {
|
||||
// if the attribute value didn't close + self-closing tag
|
||||
// eg: `<Component test={{a:1} />`
|
||||
// acorn may throw a `Unterminated regular expression` because of `/>`
|
||||
const pos = error.position?.[0];
|
||||
if (pos !== undefined && parser.template.slice(pos - 1, pos + 1) === '/>') {
|
||||
parser.index = pos;
|
||||
e.expected_token(pos, quote_mark || '}');
|
||||
}
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (value.length === 0 && !quote_mark) {
|
||||
e.expected_attribute_value(parser.index);
|
||||
}
|
||||
|
||||
if (quote_mark) parser.index += 1;
|
||||
|
||||
if (quote_mark || value.length > 1 || value[0].type === 'Text') {
|
||||
return value;
|
||||
} else {
|
||||
return value[0];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Parser} parser
|
||||
* @param {() => boolean} done
|
||||
* @param {string} location
|
||||
* @returns {any[]}
|
||||
*/
|
||||
function read_sequence(parser, done, location) {
|
||||
/** @type {AST.Text} */
|
||||
let current_chunk = {
|
||||
start: parser.index,
|
||||
end: -1,
|
||||
type: 'Text',
|
||||
raw: '',
|
||||
data: ''
|
||||
};
|
||||
|
||||
/** @type {Array<AST.Text | AST.ExpressionTag>} */
|
||||
const chunks = [];
|
||||
|
||||
/** @param {number} end */
|
||||
function flush(end) {
|
||||
if (current_chunk.raw) {
|
||||
current_chunk.data = decode_character_references(current_chunk.raw, true);
|
||||
current_chunk.end = end;
|
||||
chunks.push(current_chunk);
|
||||
}
|
||||
}
|
||||
|
||||
while (parser.index < parser.template.length) {
|
||||
const index = parser.index;
|
||||
|
||||
if (done()) {
|
||||
flush(parser.index);
|
||||
return chunks;
|
||||
} else if (parser.eat('{')) {
|
||||
if (parser.match('#')) {
|
||||
const index = parser.index - 1;
|
||||
parser.eat('#');
|
||||
const name = parser.read_until(/[^a-z]/);
|
||||
e.block_invalid_placement(index, name, location);
|
||||
} else if (parser.match('@')) {
|
||||
const index = parser.index - 1;
|
||||
parser.eat('@');
|
||||
const name = parser.read_until(/[^a-z]/);
|
||||
e.tag_invalid_placement(index, name, location);
|
||||
}
|
||||
|
||||
flush(parser.index - 1);
|
||||
|
||||
parser.allow_whitespace();
|
||||
const expression = read_expression(parser);
|
||||
parser.allow_whitespace();
|
||||
parser.eat('}', true);
|
||||
|
||||
/** @type {AST.ExpressionTag} */
|
||||
const chunk = {
|
||||
type: 'ExpressionTag',
|
||||
start: index,
|
||||
end: parser.index,
|
||||
expression,
|
||||
metadata: {
|
||||
expression: create_expression_metadata()
|
||||
}
|
||||
};
|
||||
|
||||
chunks.push(chunk);
|
||||
|
||||
current_chunk = {
|
||||
start: parser.index,
|
||||
end: -1,
|
||||
type: 'Text',
|
||||
raw: '',
|
||||
data: ''
|
||||
};
|
||||
} else {
|
||||
current_chunk.raw += parser.template[parser.index++];
|
||||
}
|
||||
}
|
||||
|
||||
if (parser.loose) {
|
||||
return chunks;
|
||||
} else {
|
||||
e.unexpected_eof(parser.template.length);
|
||||
}
|
||||
}
|
||||
17
node_modules/svelte/src/compiler/phases/1-parse/state/fragment.js
generated
vendored
Normal file
17
node_modules/svelte/src/compiler/phases/1-parse/state/fragment.js
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
/** @import { Parser } from '../index.js' */
|
||||
import element from './element.js';
|
||||
import tag from './tag.js';
|
||||
import text from './text.js';
|
||||
|
||||
/** @param {Parser} parser */
|
||||
export default function fragment(parser) {
|
||||
if (parser.match('<')) {
|
||||
return element;
|
||||
}
|
||||
|
||||
if (parser.match('{')) {
|
||||
return tag;
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
715
node_modules/svelte/src/compiler/phases/1-parse/state/tag.js
generated
vendored
Normal file
715
node_modules/svelte/src/compiler/phases/1-parse/state/tag.js
generated
vendored
Normal file
@@ -0,0 +1,715 @@
|
||||
/** @import { ArrowFunctionExpression, Expression, Identifier, Pattern } from 'estree' */
|
||||
/** @import { AST } from '#compiler' */
|
||||
/** @import { Parser } from '../index.js' */
|
||||
import { walk } from 'zimmerframe';
|
||||
import * as e from '../../../errors.js';
|
||||
import { create_expression_metadata } from '../../nodes.js';
|
||||
import { parse_expression_at } from '../acorn.js';
|
||||
import read_pattern from '../read/context.js';
|
||||
import read_expression, { get_loose_identifier } from '../read/expression.js';
|
||||
import { create_fragment } from '../utils/create.js';
|
||||
|
||||
const regex_whitespace_with_closing_curly_brace = /^\s*}/;
|
||||
|
||||
/** @param {Parser} parser */
|
||||
export default function tag(parser) {
|
||||
const start = parser.index;
|
||||
parser.index += 1;
|
||||
|
||||
parser.allow_whitespace();
|
||||
|
||||
if (parser.eat('#')) return open(parser);
|
||||
if (parser.eat(':')) return next(parser);
|
||||
if (parser.eat('@')) return special(parser);
|
||||
if (parser.match('/')) {
|
||||
if (!parser.match('/*') && !parser.match('//')) {
|
||||
parser.eat('/');
|
||||
return close(parser);
|
||||
}
|
||||
}
|
||||
|
||||
const expression = read_expression(parser);
|
||||
|
||||
parser.allow_whitespace();
|
||||
parser.eat('}', true);
|
||||
|
||||
parser.append({
|
||||
type: 'ExpressionTag',
|
||||
start,
|
||||
end: parser.index,
|
||||
expression,
|
||||
metadata: {
|
||||
expression: create_expression_metadata()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** @param {Parser} parser */
|
||||
function open(parser) {
|
||||
let start = parser.index - 2;
|
||||
while (parser.template[start] !== '{') start -= 1;
|
||||
|
||||
if (parser.eat('if')) {
|
||||
parser.require_whitespace();
|
||||
|
||||
/** @type {AST.IfBlock} */
|
||||
const block = parser.append({
|
||||
type: 'IfBlock',
|
||||
elseif: false,
|
||||
start,
|
||||
end: -1,
|
||||
test: read_expression(parser),
|
||||
consequent: create_fragment(),
|
||||
alternate: null
|
||||
});
|
||||
|
||||
parser.allow_whitespace();
|
||||
parser.eat('}', true);
|
||||
|
||||
parser.stack.push(block);
|
||||
parser.fragments.push(block.consequent);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (parser.eat('each')) {
|
||||
parser.require_whitespace();
|
||||
|
||||
const template = parser.template;
|
||||
let end = parser.template.length;
|
||||
|
||||
/** @type {Expression | undefined} */
|
||||
let expression;
|
||||
|
||||
// we have to do this loop because `{#each x as { y = z }}` fails to parse —
|
||||
// the `as { y = z }` is treated as an Expression but it's actually a Pattern.
|
||||
// the 'fix' is to backtrack and hide everything from the `as` onwards, until
|
||||
// we get a valid expression
|
||||
while (!expression) {
|
||||
try {
|
||||
expression = read_expression(parser, undefined, true);
|
||||
} catch (err) {
|
||||
end = /** @type {any} */ (err).position[0] - 2;
|
||||
|
||||
while (end > start && parser.template.slice(end, end + 2) !== 'as') {
|
||||
end -= 1;
|
||||
}
|
||||
|
||||
if (end <= start) {
|
||||
if (parser.loose) {
|
||||
expression = get_loose_identifier(parser);
|
||||
if (expression) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
|
||||
// @ts-expect-error parser.template is meant to be readonly, this is a special case
|
||||
parser.template = template.slice(0, end);
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-expect-error
|
||||
parser.template = template;
|
||||
|
||||
parser.allow_whitespace();
|
||||
|
||||
// {#each} blocks must declare a context – {#each list as item}
|
||||
if (!parser.match('as')) {
|
||||
// this could be a TypeScript assertion that was erroneously eaten.
|
||||
|
||||
if (expression.type === 'SequenceExpression') {
|
||||
expression = expression.expressions[0];
|
||||
}
|
||||
|
||||
let assertion = null;
|
||||
let end = expression.end;
|
||||
|
||||
expression = walk(expression, null, {
|
||||
// @ts-expect-error
|
||||
TSAsExpression(node, context) {
|
||||
if (node.end === /** @type {Expression} */ (expression).end) {
|
||||
assertion = node;
|
||||
end = node.expression.end;
|
||||
return node.expression;
|
||||
}
|
||||
|
||||
context.next();
|
||||
}
|
||||
});
|
||||
|
||||
expression.end = end;
|
||||
|
||||
if (assertion) {
|
||||
// we can't reset `parser.index` to `expression.expression.end` because
|
||||
// it will ignore any parentheses — we need to jump through this hoop
|
||||
let end = /** @type {any} */ (/** @type {any} */ (assertion).typeAnnotation).start - 2;
|
||||
while (parser.template.slice(end, end + 2) !== 'as') end -= 1;
|
||||
|
||||
parser.index = end;
|
||||
}
|
||||
}
|
||||
|
||||
/** @type {Pattern | null} */
|
||||
let context = null;
|
||||
let index;
|
||||
let key;
|
||||
|
||||
if (parser.eat('as')) {
|
||||
parser.require_whitespace();
|
||||
|
||||
context = read_pattern(parser);
|
||||
} else {
|
||||
// {#each Array.from({ length: 10 }), i} is read as a sequence expression,
|
||||
// which is set back above - we now gotta reset the index as a consequence
|
||||
// to properly read the , i part
|
||||
parser.index = /** @type {number} */ (expression.end);
|
||||
}
|
||||
|
||||
parser.allow_whitespace();
|
||||
|
||||
if (parser.eat(',')) {
|
||||
parser.allow_whitespace();
|
||||
index = parser.read_identifier();
|
||||
if (!index) {
|
||||
e.expected_identifier(parser.index);
|
||||
}
|
||||
|
||||
parser.allow_whitespace();
|
||||
}
|
||||
|
||||
if (parser.eat('(')) {
|
||||
parser.allow_whitespace();
|
||||
|
||||
key = read_expression(parser, '(');
|
||||
parser.allow_whitespace();
|
||||
parser.eat(')', true);
|
||||
parser.allow_whitespace();
|
||||
}
|
||||
|
||||
const matches = parser.eat('}', true, false);
|
||||
|
||||
if (!matches) {
|
||||
// Parser may have read the `as` as part of the expression (e.g. in `{#each foo. as x}`)
|
||||
if (parser.template.slice(parser.index - 4, parser.index) === ' as ') {
|
||||
const prev_index = parser.index;
|
||||
context = read_pattern(parser);
|
||||
parser.eat('}', true);
|
||||
expression = {
|
||||
type: 'Identifier',
|
||||
name: '',
|
||||
start: expression.start,
|
||||
end: prev_index - 4
|
||||
};
|
||||
} else {
|
||||
parser.eat('}', true); // rerun to produce the parser error
|
||||
}
|
||||
}
|
||||
|
||||
/** @type {AST.EachBlock} */
|
||||
const block = parser.append({
|
||||
type: 'EachBlock',
|
||||
start,
|
||||
end: -1,
|
||||
expression,
|
||||
body: create_fragment(),
|
||||
context,
|
||||
index,
|
||||
key,
|
||||
metadata: /** @type {any} */ (null) // filled in later
|
||||
});
|
||||
|
||||
parser.stack.push(block);
|
||||
parser.fragments.push(block.body);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (parser.eat('await')) {
|
||||
parser.require_whitespace();
|
||||
const expression = read_expression(parser);
|
||||
parser.allow_whitespace();
|
||||
|
||||
/** @type {AST.AwaitBlock} */
|
||||
const block = parser.append({
|
||||
type: 'AwaitBlock',
|
||||
start,
|
||||
end: -1,
|
||||
expression,
|
||||
value: null,
|
||||
error: null,
|
||||
pending: null,
|
||||
then: null,
|
||||
catch: null
|
||||
});
|
||||
|
||||
if (parser.eat('then')) {
|
||||
if (parser.match_regex(regex_whitespace_with_closing_curly_brace)) {
|
||||
parser.allow_whitespace();
|
||||
} else {
|
||||
parser.require_whitespace();
|
||||
block.value = read_pattern(parser);
|
||||
parser.allow_whitespace();
|
||||
}
|
||||
|
||||
block.then = create_fragment();
|
||||
parser.fragments.push(block.then);
|
||||
} else if (parser.eat('catch')) {
|
||||
if (parser.match_regex(regex_whitespace_with_closing_curly_brace)) {
|
||||
parser.allow_whitespace();
|
||||
} else {
|
||||
parser.require_whitespace();
|
||||
block.error = read_pattern(parser);
|
||||
parser.allow_whitespace();
|
||||
}
|
||||
|
||||
block.catch = create_fragment();
|
||||
parser.fragments.push(block.catch);
|
||||
} else {
|
||||
block.pending = create_fragment();
|
||||
parser.fragments.push(block.pending);
|
||||
}
|
||||
|
||||
const matches = parser.eat('}', true, false);
|
||||
|
||||
// Parser may have read the `then/catch` as part of the expression (e.g. in `{#await foo. then x}`)
|
||||
if (!matches) {
|
||||
if (parser.template.slice(parser.index - 6, parser.index) === ' then ') {
|
||||
const prev_index = parser.index;
|
||||
block.value = read_pattern(parser);
|
||||
parser.eat('}', true);
|
||||
block.expression = {
|
||||
type: 'Identifier',
|
||||
name: '',
|
||||
start: expression.start,
|
||||
end: prev_index - 6
|
||||
};
|
||||
block.then = block.pending;
|
||||
block.pending = null;
|
||||
} else if (parser.template.slice(parser.index - 7, parser.index) === ' catch ') {
|
||||
const prev_index = parser.index;
|
||||
block.error = read_pattern(parser);
|
||||
parser.eat('}', true);
|
||||
block.expression = {
|
||||
type: 'Identifier',
|
||||
name: '',
|
||||
start: expression.start,
|
||||
end: prev_index - 7
|
||||
};
|
||||
block.catch = block.pending;
|
||||
block.pending = null;
|
||||
} else {
|
||||
parser.eat('}', true); // rerun to produce the parser error
|
||||
}
|
||||
}
|
||||
|
||||
parser.stack.push(block);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (parser.eat('key')) {
|
||||
parser.require_whitespace();
|
||||
|
||||
const expression = read_expression(parser);
|
||||
parser.allow_whitespace();
|
||||
|
||||
parser.eat('}', true);
|
||||
|
||||
/** @type {AST.KeyBlock} */
|
||||
const block = parser.append({
|
||||
type: 'KeyBlock',
|
||||
start,
|
||||
end: -1,
|
||||
expression,
|
||||
fragment: create_fragment()
|
||||
});
|
||||
|
||||
parser.stack.push(block);
|
||||
parser.fragments.push(block.fragment);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (parser.eat('snippet')) {
|
||||
parser.require_whitespace();
|
||||
|
||||
const name_start = parser.index;
|
||||
let name = parser.read_identifier();
|
||||
const name_end = parser.index;
|
||||
|
||||
if (name === null) {
|
||||
if (parser.loose) {
|
||||
name = '';
|
||||
} else {
|
||||
e.expected_identifier(parser.index);
|
||||
}
|
||||
}
|
||||
|
||||
parser.allow_whitespace();
|
||||
|
||||
const params_start = parser.index;
|
||||
|
||||
const matched = parser.eat('(', true, false);
|
||||
|
||||
if (matched) {
|
||||
let parentheses = 1;
|
||||
|
||||
while (parser.index < parser.template.length && (!parser.match(')') || parentheses !== 1)) {
|
||||
if (parser.match('(')) parentheses++;
|
||||
if (parser.match(')')) parentheses--;
|
||||
parser.index += 1;
|
||||
}
|
||||
|
||||
parser.eat(')', true);
|
||||
}
|
||||
|
||||
const prelude = parser.template.slice(0, params_start).replace(/\S/g, ' ');
|
||||
const params = parser.template.slice(params_start, parser.index);
|
||||
|
||||
let function_expression = matched
|
||||
? /** @type {ArrowFunctionExpression} */ (
|
||||
parse_expression_at(prelude + `${params} => {}`, parser.ts, params_start)
|
||||
)
|
||||
: { params: [] };
|
||||
|
||||
parser.allow_whitespace();
|
||||
parser.eat('}', true);
|
||||
|
||||
/** @type {AST.SnippetBlock} */
|
||||
const block = parser.append({
|
||||
type: 'SnippetBlock',
|
||||
start,
|
||||
end: -1,
|
||||
expression: {
|
||||
type: 'Identifier',
|
||||
start: name_start,
|
||||
end: name_end,
|
||||
name
|
||||
},
|
||||
parameters: function_expression.params,
|
||||
body: create_fragment(),
|
||||
metadata: {
|
||||
can_hoist: false,
|
||||
sites: new Set()
|
||||
}
|
||||
});
|
||||
parser.stack.push(block);
|
||||
parser.fragments.push(block.body);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
e.expected_block_type(parser.index);
|
||||
}
|
||||
|
||||
/** @param {Parser} parser */
|
||||
function next(parser) {
|
||||
const start = parser.index - 1;
|
||||
|
||||
const block = parser.current(); // TODO type should not be TemplateNode, that's much too broad
|
||||
|
||||
if (block.type === 'IfBlock') {
|
||||
if (!parser.eat('else')) e.expected_token(start, '{:else} or {:else if}');
|
||||
if (parser.eat('if')) e.block_invalid_elseif(start);
|
||||
|
||||
parser.allow_whitespace();
|
||||
|
||||
parser.fragments.pop();
|
||||
|
||||
block.alternate = create_fragment();
|
||||
parser.fragments.push(block.alternate);
|
||||
|
||||
// :else if
|
||||
if (parser.eat('if')) {
|
||||
parser.require_whitespace();
|
||||
|
||||
const expression = read_expression(parser);
|
||||
|
||||
parser.allow_whitespace();
|
||||
parser.eat('}', true);
|
||||
|
||||
let elseif_start = start - 1;
|
||||
while (parser.template[elseif_start] !== '{') elseif_start -= 1;
|
||||
|
||||
/** @type {AST.IfBlock} */
|
||||
const child = parser.append({
|
||||
start: elseif_start,
|
||||
end: -1,
|
||||
type: 'IfBlock',
|
||||
elseif: true,
|
||||
test: expression,
|
||||
consequent: create_fragment(),
|
||||
alternate: null
|
||||
});
|
||||
|
||||
parser.stack.push(child);
|
||||
parser.fragments.pop();
|
||||
parser.fragments.push(child.consequent);
|
||||
} else {
|
||||
// :else
|
||||
parser.allow_whitespace();
|
||||
parser.eat('}', true);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (block.type === 'EachBlock') {
|
||||
if (!parser.eat('else')) e.expected_token(start, '{:else}');
|
||||
|
||||
parser.allow_whitespace();
|
||||
parser.eat('}', true);
|
||||
|
||||
block.fallback = create_fragment();
|
||||
|
||||
parser.fragments.pop();
|
||||
parser.fragments.push(block.fallback);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (block.type === 'AwaitBlock') {
|
||||
if (parser.eat('then')) {
|
||||
if (block.then) {
|
||||
e.block_duplicate_clause(start, '{:then}');
|
||||
}
|
||||
|
||||
if (!parser.eat('}')) {
|
||||
parser.require_whitespace();
|
||||
block.value = read_pattern(parser);
|
||||
parser.allow_whitespace();
|
||||
parser.eat('}', true);
|
||||
}
|
||||
|
||||
block.then = create_fragment();
|
||||
parser.fragments.pop();
|
||||
parser.fragments.push(block.then);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (parser.eat('catch')) {
|
||||
if (block.catch) {
|
||||
e.block_duplicate_clause(start, '{:catch}');
|
||||
}
|
||||
|
||||
if (!parser.eat('}')) {
|
||||
parser.require_whitespace();
|
||||
block.error = read_pattern(parser);
|
||||
parser.allow_whitespace();
|
||||
parser.eat('}', true);
|
||||
}
|
||||
|
||||
block.catch = create_fragment();
|
||||
parser.fragments.pop();
|
||||
parser.fragments.push(block.catch);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
e.expected_token(start, '{:then ...} or {:catch ...}');
|
||||
}
|
||||
|
||||
e.block_invalid_continuation_placement(start);
|
||||
}
|
||||
|
||||
/** @param {Parser} parser */
|
||||
function close(parser) {
|
||||
const start = parser.index - 1;
|
||||
|
||||
let block = parser.current();
|
||||
/** Only relevant/reached for loose parsing mode */
|
||||
let matched;
|
||||
|
||||
switch (block.type) {
|
||||
case 'IfBlock':
|
||||
matched = parser.eat('if', true, false);
|
||||
|
||||
if (!matched) {
|
||||
block.end = start - 1;
|
||||
parser.pop();
|
||||
close(parser);
|
||||
return;
|
||||
}
|
||||
|
||||
parser.allow_whitespace();
|
||||
parser.eat('}', true);
|
||||
|
||||
while (block.elseif) {
|
||||
block.end = parser.index;
|
||||
parser.stack.pop();
|
||||
block = /** @type {AST.IfBlock} */ (parser.current());
|
||||
}
|
||||
|
||||
block.end = parser.index;
|
||||
parser.pop();
|
||||
return;
|
||||
|
||||
case 'EachBlock':
|
||||
matched = parser.eat('each', true, false);
|
||||
break;
|
||||
case 'KeyBlock':
|
||||
matched = parser.eat('key', true, false);
|
||||
break;
|
||||
case 'AwaitBlock':
|
||||
matched = parser.eat('await', true, false);
|
||||
break;
|
||||
case 'SnippetBlock':
|
||||
matched = parser.eat('snippet', true, false);
|
||||
break;
|
||||
|
||||
case 'RegularElement':
|
||||
if (parser.loose) {
|
||||
matched = false;
|
||||
} else {
|
||||
// TODO handle implicitly closed elements
|
||||
e.block_unexpected_close(start);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
e.block_unexpected_close(start);
|
||||
}
|
||||
|
||||
if (!matched) {
|
||||
block.end = start - 1;
|
||||
parser.pop();
|
||||
close(parser);
|
||||
return;
|
||||
}
|
||||
|
||||
parser.allow_whitespace();
|
||||
parser.eat('}', true);
|
||||
block.end = parser.index;
|
||||
parser.pop();
|
||||
}
|
||||
|
||||
/** @param {Parser} parser */
|
||||
function special(parser) {
|
||||
let start = parser.index;
|
||||
while (parser.template[start] !== '{') start -= 1;
|
||||
|
||||
if (parser.eat('html')) {
|
||||
// {@html content} tag
|
||||
parser.require_whitespace();
|
||||
|
||||
const expression = read_expression(parser);
|
||||
|
||||
parser.allow_whitespace();
|
||||
parser.eat('}', true);
|
||||
|
||||
parser.append({
|
||||
type: 'HtmlTag',
|
||||
start,
|
||||
end: parser.index,
|
||||
expression
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (parser.eat('debug')) {
|
||||
/** @type {Identifier[]} */
|
||||
let identifiers;
|
||||
|
||||
// Implies {@debug} which indicates "debug all"
|
||||
if (parser.read(regex_whitespace_with_closing_curly_brace)) {
|
||||
identifiers = [];
|
||||
} else {
|
||||
const expression = read_expression(parser);
|
||||
|
||||
identifiers =
|
||||
expression.type === 'SequenceExpression'
|
||||
? /** @type {Identifier[]} */ (expression.expressions)
|
||||
: [/** @type {Identifier} */ (expression)];
|
||||
|
||||
identifiers.forEach(
|
||||
/** @param {any} node */ (node) => {
|
||||
if (node.type !== 'Identifier') {
|
||||
e.debug_tag_invalid_arguments(/** @type {number} */ (node.start));
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
parser.allow_whitespace();
|
||||
parser.eat('}', true);
|
||||
}
|
||||
|
||||
parser.append({
|
||||
type: 'DebugTag',
|
||||
start,
|
||||
end: parser.index,
|
||||
identifiers
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (parser.eat('const')) {
|
||||
parser.require_whitespace();
|
||||
|
||||
const id = read_pattern(parser);
|
||||
parser.allow_whitespace();
|
||||
|
||||
parser.eat('=', true);
|
||||
parser.allow_whitespace();
|
||||
|
||||
const expression_start = parser.index;
|
||||
const init = read_expression(parser);
|
||||
if (
|
||||
init.type === 'SequenceExpression' &&
|
||||
!parser.template.substring(expression_start, init.start).includes('(')
|
||||
) {
|
||||
// const a = (b, c) is allowed but a = b, c = d is not;
|
||||
e.const_tag_invalid_expression(init);
|
||||
}
|
||||
parser.allow_whitespace();
|
||||
|
||||
parser.eat('}', true);
|
||||
|
||||
parser.append({
|
||||
type: 'ConstTag',
|
||||
start,
|
||||
end: parser.index,
|
||||
declaration: {
|
||||
type: 'VariableDeclaration',
|
||||
kind: 'const',
|
||||
declarations: [{ type: 'VariableDeclarator', id, init, start: id.start, end: init.end }],
|
||||
start: start + 2, // start at const, not at @const
|
||||
end: parser.index - 1
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (parser.eat('render')) {
|
||||
// {@render foo(...)}
|
||||
parser.require_whitespace();
|
||||
|
||||
const expression = read_expression(parser);
|
||||
|
||||
if (
|
||||
expression.type !== 'CallExpression' &&
|
||||
(expression.type !== 'ChainExpression' || expression.expression.type !== 'CallExpression')
|
||||
) {
|
||||
e.render_tag_invalid_expression(expression);
|
||||
}
|
||||
|
||||
parser.allow_whitespace();
|
||||
parser.eat('}', true);
|
||||
|
||||
parser.append({
|
||||
type: 'RenderTag',
|
||||
start,
|
||||
end: parser.index,
|
||||
expression: /** @type {AST.RenderTag['expression']} */ (expression),
|
||||
metadata: {
|
||||
dynamic: false,
|
||||
arguments: [],
|
||||
path: [],
|
||||
snippets: new Set()
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
23
node_modules/svelte/src/compiler/phases/1-parse/state/text.js
generated
vendored
Normal file
23
node_modules/svelte/src/compiler/phases/1-parse/state/text.js
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
/** @import { AST } from '#compiler' */
|
||||
/** @import { Parser } from '../index.js' */
|
||||
import { decode_character_references } from '../utils/html.js';
|
||||
|
||||
/** @param {Parser} parser */
|
||||
export default function text(parser) {
|
||||
const start = parser.index;
|
||||
|
||||
let data = '';
|
||||
|
||||
while (parser.index < parser.template.length && !parser.match('<') && !parser.match('{')) {
|
||||
data += parser.template[parser.index++];
|
||||
}
|
||||
|
||||
/** @type {AST.Text} */
|
||||
parser.append({
|
||||
type: 'Text',
|
||||
start,
|
||||
end: parser.index,
|
||||
raw: data,
|
||||
data: decode_character_references(data, false)
|
||||
});
|
||||
}
|
||||
164
node_modules/svelte/src/compiler/phases/1-parse/utils/bracket.js
generated
vendored
Normal file
164
node_modules/svelte/src/compiler/phases/1-parse/utils/bracket.js
generated
vendored
Normal file
@@ -0,0 +1,164 @@
|
||||
const SQUARE_BRACKET_OPEN = '[';
|
||||
const SQUARE_BRACKET_CLOSE = ']';
|
||||
const CURLY_BRACKET_OPEN = '{';
|
||||
const CURLY_BRACKET_CLOSE = '}';
|
||||
const PARENTHESES_OPEN = '(';
|
||||
const PARENTHESES_CLOSE = ')';
|
||||
|
||||
/** @param {string} char */
|
||||
export function is_bracket_open(char) {
|
||||
return char === SQUARE_BRACKET_OPEN || char === CURLY_BRACKET_OPEN;
|
||||
}
|
||||
|
||||
/** @param {string} char */
|
||||
export function is_bracket_close(char) {
|
||||
return char === SQUARE_BRACKET_CLOSE || char === CURLY_BRACKET_CLOSE;
|
||||
}
|
||||
|
||||
/** @param {string} open */
|
||||
export function get_bracket_close(open) {
|
||||
if (open === SQUARE_BRACKET_OPEN) {
|
||||
return SQUARE_BRACKET_CLOSE;
|
||||
}
|
||||
|
||||
if (open === CURLY_BRACKET_OPEN) {
|
||||
return CURLY_BRACKET_CLOSE;
|
||||
}
|
||||
|
||||
if (open === PARENTHESES_OPEN) {
|
||||
return PARENTHESES_CLOSE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} num
|
||||
* @returns {number} Infinity if {@link num} is negative, else {@link num}.
|
||||
*/
|
||||
function infinity_if_negative(num) {
|
||||
if (num < 0) {
|
||||
return Infinity;
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} string The string to search.
|
||||
* @param {number} search_start_index The index to start searching at.
|
||||
* @param {"'" | '"' | '`'} string_start_char The character that started this string.
|
||||
* @returns {number} The index of the end of this string expression, or `Infinity` if not found.
|
||||
*/
|
||||
function find_string_end(string, search_start_index, string_start_char) {
|
||||
let string_to_search;
|
||||
if (string_start_char === '`') {
|
||||
string_to_search = string;
|
||||
} else {
|
||||
// we could slice at the search start index, but this way the index remains valid
|
||||
string_to_search = string.slice(
|
||||
0,
|
||||
infinity_if_negative(string.indexOf('\n', search_start_index))
|
||||
);
|
||||
}
|
||||
|
||||
return find_unescaped_char(string_to_search, search_start_index, string_start_char);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} string The string to search.
|
||||
* @param {number} search_start_index The index to start searching at.
|
||||
* @returns {number} The index of the end of this regex expression, or `Infinity` if not found.
|
||||
*/
|
||||
function find_regex_end(string, search_start_index) {
|
||||
return find_unescaped_char(string, search_start_index, '/');
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} string The string to search.
|
||||
* @param {number} search_start_index The index to begin the search at.
|
||||
* @param {string} char The character to search for.
|
||||
* @returns {number} The index of the first unescaped instance of {@link char}, or `Infinity` if not found.
|
||||
*/
|
||||
function find_unescaped_char(string, search_start_index, char) {
|
||||
let i = search_start_index;
|
||||
while (true) {
|
||||
const found_index = string.indexOf(char, i);
|
||||
if (found_index === -1) {
|
||||
return Infinity;
|
||||
}
|
||||
if (count_leading_backslashes(string, found_index - 1) % 2 === 0) {
|
||||
return found_index;
|
||||
}
|
||||
i = found_index + 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Count consecutive leading backslashes before {@link search_start_index}.
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* count_leading_backslashes('\\\\\\foo', 2); // 3 (the backslashes have to be escaped in the string literal, there are three in reality)
|
||||
* ```
|
||||
*
|
||||
* @param {string} string The string to search.
|
||||
* @param {number} search_start_index The index to begin the search at.
|
||||
*/
|
||||
function count_leading_backslashes(string, search_start_index) {
|
||||
let i = search_start_index;
|
||||
let count = 0;
|
||||
while (string[i] === '\\') {
|
||||
count++;
|
||||
i--;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the corresponding closing bracket, ignoring brackets found inside comments, strings, or regex expressions.
|
||||
* @param {string} template The string to search.
|
||||
* @param {number} index The index to begin the search at.
|
||||
* @param {string} open The opening bracket (ex: `'{'` will search for `'}'`).
|
||||
* @returns {number | undefined} The index of the closing bracket, or undefined if not found.
|
||||
*/
|
||||
export function find_matching_bracket(template, index, open) {
|
||||
const close = get_bracket_close(open);
|
||||
let brackets = 1;
|
||||
let i = index;
|
||||
while (brackets > 0 && i < template.length) {
|
||||
const char = template[i];
|
||||
switch (char) {
|
||||
case "'":
|
||||
case '"':
|
||||
case '`':
|
||||
i = find_string_end(template, i + 1, char) + 1;
|
||||
continue;
|
||||
case '/': {
|
||||
const next_char = template[i + 1];
|
||||
if (!next_char) continue;
|
||||
if (next_char === '/') {
|
||||
i = infinity_if_negative(template.indexOf('\n', i + 1)) + '\n'.length;
|
||||
continue;
|
||||
}
|
||||
if (next_char === '*') {
|
||||
i = infinity_if_negative(template.indexOf('*/', i + 1)) + '*/'.length;
|
||||
continue;
|
||||
}
|
||||
i = find_regex_end(template, i + 1) + '/'.length;
|
||||
continue;
|
||||
}
|
||||
default: {
|
||||
const char = template[i];
|
||||
if (char === open) {
|
||||
brackets++;
|
||||
} else if (char === close) {
|
||||
brackets--;
|
||||
}
|
||||
if (brackets === 0) {
|
||||
return i;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
16
node_modules/svelte/src/compiler/phases/1-parse/utils/create.js
generated
vendored
Normal file
16
node_modules/svelte/src/compiler/phases/1-parse/utils/create.js
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
/** @import { AST } from '#compiler' */
|
||||
|
||||
/**
|
||||
* @param {any} transparent
|
||||
* @returns {AST.Fragment}
|
||||
*/
|
||||
export function create_fragment(transparent = false) {
|
||||
return {
|
||||
type: 'Fragment',
|
||||
nodes: [],
|
||||
metadata: {
|
||||
transparent,
|
||||
dynamic: false
|
||||
}
|
||||
};
|
||||
}
|
||||
2234
node_modules/svelte/src/compiler/phases/1-parse/utils/entities.js
generated
vendored
Normal file
2234
node_modules/svelte/src/compiler/phases/1-parse/utils/entities.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
280
node_modules/svelte/src/compiler/phases/1-parse/utils/fuzzymatch.js
generated
vendored
Normal file
280
node_modules/svelte/src/compiler/phases/1-parse/utils/fuzzymatch.js
generated
vendored
Normal file
@@ -0,0 +1,280 @@
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {string[]} names
|
||||
* @returns {string | null}
|
||||
*/
|
||||
export default function fuzzymatch(name, names) {
|
||||
if (names.length === 0) return null;
|
||||
|
||||
const set = new FuzzySet(names);
|
||||
const matches = set.get(name);
|
||||
|
||||
return matches && matches[0][0] > 0.7 ? matches[0][1] : null;
|
||||
}
|
||||
|
||||
// adapted from https://github.com/Glench/fuzzyset.js/blob/master/lib/fuzzyset.js
|
||||
// BSD Licensed
|
||||
|
||||
const GRAM_SIZE_LOWER = 2;
|
||||
const GRAM_SIZE_UPPER = 3;
|
||||
|
||||
// return an edit distance from 0 to 1
|
||||
|
||||
/**
|
||||
* @param {string} str1
|
||||
* @param {string} str2
|
||||
*/
|
||||
function _distance(str1, str2) {
|
||||
if (str1 === null && str2 === null) {
|
||||
throw 'Trying to compare two null values';
|
||||
}
|
||||
if (str1 === null || str2 === null) return 0;
|
||||
str1 = String(str1);
|
||||
str2 = String(str2);
|
||||
|
||||
const distance = levenshtein(str1, str2);
|
||||
return 1 - distance / Math.max(str1.length, str2.length);
|
||||
}
|
||||
|
||||
// helper functions
|
||||
|
||||
/**
|
||||
* @param {string} str1
|
||||
* @param {string} str2
|
||||
*/
|
||||
function levenshtein(str1, str2) {
|
||||
/** @type {number[]} */
|
||||
const current = [];
|
||||
let prev = 0;
|
||||
let value = 0;
|
||||
|
||||
for (let i = 0; i <= str2.length; i++) {
|
||||
for (let j = 0; j <= str1.length; j++) {
|
||||
if (i && j) {
|
||||
if (str1.charAt(j - 1) === str2.charAt(i - 1)) {
|
||||
value = prev;
|
||||
} else {
|
||||
value = Math.min(current[j], current[j - 1], prev) + 1;
|
||||
}
|
||||
} else {
|
||||
value = i + j;
|
||||
}
|
||||
|
||||
prev = current[j];
|
||||
current[j] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return /** @type {number} */ (current.pop());
|
||||
}
|
||||
|
||||
const non_word_regex = /[^\w, ]+/;
|
||||
|
||||
/**
|
||||
* @param {string} value
|
||||
* @param {any} gram_size
|
||||
*/
|
||||
function iterate_grams(value, gram_size = 2) {
|
||||
const simplified = '-' + value.toLowerCase().replace(non_word_regex, '') + '-';
|
||||
const len_diff = gram_size - simplified.length;
|
||||
const results = [];
|
||||
|
||||
if (len_diff > 0) {
|
||||
for (let i = 0; i < len_diff; ++i) {
|
||||
value += '-';
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < simplified.length - gram_size + 1; ++i) {
|
||||
results.push(simplified.slice(i, i + gram_size));
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} value
|
||||
* @param {any} gram_size
|
||||
*/
|
||||
function gram_counter(value, gram_size = 2) {
|
||||
// return an object where key=gram, value=number of occurrences
|
||||
|
||||
/** @type {Record<string, number>} */
|
||||
const result = {};
|
||||
const grams = iterate_grams(value, gram_size);
|
||||
let i = 0;
|
||||
|
||||
for (i; i < grams.length; ++i) {
|
||||
if (grams[i] in result) {
|
||||
result[grams[i]] += 1;
|
||||
} else {
|
||||
result[grams[i]] = 1;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {MatchTuple} a
|
||||
* @param {MatchTuple} b
|
||||
*/
|
||||
function sort_descending(a, b) {
|
||||
return b[0] - a[0];
|
||||
}
|
||||
|
||||
class FuzzySet {
|
||||
/** @type {Record<string, string>} */
|
||||
exact_set = {};
|
||||
|
||||
/** @type {Record<string, [number, number][]>} */
|
||||
match_dict = {};
|
||||
|
||||
/** @type {Record<string, number[]>} */
|
||||
items = {};
|
||||
|
||||
/** @param {string[]} arr */
|
||||
constructor(arr) {
|
||||
// initialisation
|
||||
for (let i = GRAM_SIZE_LOWER; i < GRAM_SIZE_UPPER + 1; ++i) {
|
||||
this.items[i] = [];
|
||||
}
|
||||
|
||||
// add all the items to the set
|
||||
for (let i = 0; i < arr.length; ++i) {
|
||||
this.add(arr[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/** @param {string} value */
|
||||
add(value) {
|
||||
const normalized_value = value.toLowerCase();
|
||||
if (normalized_value in this.exact_set) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let i = GRAM_SIZE_LOWER;
|
||||
for (i; i < GRAM_SIZE_UPPER + 1; ++i) {
|
||||
this._add(value, i);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} value
|
||||
* @param {number} gram_size
|
||||
*/
|
||||
_add(value, gram_size) {
|
||||
const normalized_value = value.toLowerCase();
|
||||
const items = this.items[gram_size] || [];
|
||||
const index = items.length;
|
||||
|
||||
items.push(0);
|
||||
const gram_counts = gram_counter(normalized_value, gram_size);
|
||||
let sum_of_square_gram_counts = 0;
|
||||
let gram;
|
||||
let gram_count;
|
||||
|
||||
for (gram in gram_counts) {
|
||||
gram_count = gram_counts[gram];
|
||||
sum_of_square_gram_counts += Math.pow(gram_count, 2);
|
||||
if (gram in this.match_dict) {
|
||||
this.match_dict[gram].push([index, gram_count]);
|
||||
} else {
|
||||
this.match_dict[gram] = [[index, gram_count]];
|
||||
}
|
||||
}
|
||||
const vector_normal = Math.sqrt(sum_of_square_gram_counts);
|
||||
// @ts-ignore no idea what this code is doing
|
||||
items[index] = [vector_normal, normalized_value];
|
||||
this.items[gram_size] = items;
|
||||
this.exact_set[normalized_value] = value;
|
||||
}
|
||||
|
||||
/** @param {string} value */
|
||||
get(value) {
|
||||
const normalized_value = value.toLowerCase();
|
||||
const result = this.exact_set[normalized_value];
|
||||
|
||||
if (result) {
|
||||
return /** @type {MatchTuple[]} */ ([[1, result]]);
|
||||
}
|
||||
|
||||
// start with high gram size and if there are no results, go to lower gram sizes
|
||||
for (let gram_size = GRAM_SIZE_UPPER; gram_size >= GRAM_SIZE_LOWER; --gram_size) {
|
||||
const results = this.__get(value, gram_size);
|
||||
if (results.length > 0) return results;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} value
|
||||
* @param {number} gram_size
|
||||
* @returns {MatchTuple[]}
|
||||
*/
|
||||
__get(value, gram_size) {
|
||||
const normalized_value = value.toLowerCase();
|
||||
|
||||
/** @type {Record<string, number>} */
|
||||
const matches = {};
|
||||
const gram_counts = gram_counter(normalized_value, gram_size);
|
||||
const items = this.items[gram_size];
|
||||
let sum_of_square_gram_counts = 0;
|
||||
let gram;
|
||||
let gram_count;
|
||||
let i;
|
||||
let index;
|
||||
let other_gram_count;
|
||||
|
||||
for (gram in gram_counts) {
|
||||
gram_count = gram_counts[gram];
|
||||
sum_of_square_gram_counts += Math.pow(gram_count, 2);
|
||||
if (gram in this.match_dict) {
|
||||
for (i = 0; i < this.match_dict[gram].length; ++i) {
|
||||
index = this.match_dict[gram][i][0];
|
||||
other_gram_count = this.match_dict[gram][i][1];
|
||||
if (index in matches) {
|
||||
matches[index] += gram_count * other_gram_count;
|
||||
} else {
|
||||
matches[index] = gram_count * other_gram_count;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const vector_normal = Math.sqrt(sum_of_square_gram_counts);
|
||||
|
||||
/** @type {MatchTuple[]} */
|
||||
let results = [];
|
||||
let match_score;
|
||||
|
||||
// build a results list of [score, str]
|
||||
for (const match_index in matches) {
|
||||
match_score = matches[match_index];
|
||||
// @ts-ignore no idea what this code is doing
|
||||
results.push([match_score / (vector_normal * items[match_index][0]), items[match_index][1]]);
|
||||
}
|
||||
|
||||
results.sort(sort_descending);
|
||||
|
||||
/** @type {MatchTuple[]} */
|
||||
let new_results = [];
|
||||
const end_index = Math.min(50, results.length);
|
||||
// truncate somewhat arbitrarily to 50
|
||||
for (let i = 0; i < end_index; ++i) {
|
||||
// @ts-ignore no idea what this code is doing
|
||||
new_results.push([_distance(results[i][1], normalized_value), results[i][1]]);
|
||||
}
|
||||
results = new_results;
|
||||
results.sort(sort_descending);
|
||||
|
||||
new_results = [];
|
||||
for (let i = 0; i < results.length; ++i) {
|
||||
if (results[i][0] === results[0][0]) {
|
||||
// @ts-ignore no idea what this code is doing
|
||||
new_results.push([results[i][0], this.exact_set[results[i][1]]]);
|
||||
}
|
||||
}
|
||||
|
||||
return new_results;
|
||||
}
|
||||
}
|
||||
|
||||
/** @typedef {[score: number, match: string]} MatchTuple */
|
||||
120
node_modules/svelte/src/compiler/phases/1-parse/utils/html.js
generated
vendored
Normal file
120
node_modules/svelte/src/compiler/phases/1-parse/utils/html.js
generated
vendored
Normal file
@@ -0,0 +1,120 @@
|
||||
import entities from './entities.js';
|
||||
|
||||
const windows_1252 = [
|
||||
8364, 129, 8218, 402, 8222, 8230, 8224, 8225, 710, 8240, 352, 8249, 338, 141, 381, 143, 144, 8216,
|
||||
8217, 8220, 8221, 8226, 8211, 8212, 732, 8482, 353, 8250, 339, 157, 382, 376
|
||||
];
|
||||
|
||||
/**
|
||||
* @param {string} entity_name
|
||||
* @param {boolean} is_attribute_value
|
||||
*/
|
||||
function reg_exp_entity(entity_name, is_attribute_value) {
|
||||
// https://html.spec.whatwg.org/multipage/parsing.html#named-character-reference-state
|
||||
// doesn't decode the html entity which not ends with ; and next character is =, number or alphabet in attribute value.
|
||||
if (is_attribute_value && !entity_name.endsWith(';')) {
|
||||
return `${entity_name}\\b(?!=)`;
|
||||
}
|
||||
return entity_name;
|
||||
}
|
||||
|
||||
/** @param {boolean} is_attribute_value */
|
||||
function get_entity_pattern(is_attribute_value) {
|
||||
const reg_exp_num = '#(?:x[a-fA-F\\d]+|\\d+)(?:;)?';
|
||||
const reg_exp_entities = Object.keys(entities).map(
|
||||
/** @param {any} entity_name */ (entity_name) => reg_exp_entity(entity_name, is_attribute_value)
|
||||
);
|
||||
|
||||
const entity_pattern = new RegExp(`&(${reg_exp_num}|${reg_exp_entities.join('|')})`, 'g');
|
||||
|
||||
return entity_pattern;
|
||||
}
|
||||
|
||||
const entity_pattern_content = get_entity_pattern(false);
|
||||
const entity_pattern_attr_value = get_entity_pattern(true);
|
||||
|
||||
/**
|
||||
* @param {string} html
|
||||
* @param {boolean} is_attribute_value
|
||||
*/
|
||||
export function decode_character_references(html, is_attribute_value) {
|
||||
const entity_pattern = is_attribute_value ? entity_pattern_attr_value : entity_pattern_content;
|
||||
return html.replace(
|
||||
entity_pattern,
|
||||
/**
|
||||
* @param {any} match
|
||||
* @param {keyof typeof entities} entity
|
||||
*/ (match, entity) => {
|
||||
let code;
|
||||
|
||||
// Handle named entities
|
||||
if (entity[0] !== '#') {
|
||||
code = entities[entity];
|
||||
} else if (entity[1] === 'x') {
|
||||
code = parseInt(entity.substring(2), 16);
|
||||
} else {
|
||||
code = parseInt(entity.substring(1), 10);
|
||||
}
|
||||
|
||||
if (!code) {
|
||||
return match;
|
||||
}
|
||||
|
||||
return String.fromCodePoint(validate_code(code));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const NUL = 0;
|
||||
|
||||
// some code points are verboten. If we were inserting HTML, the browser would replace the illegal
|
||||
// code points with alternatives in some cases - since we're bypassing that mechanism, we need
|
||||
// to replace them ourselves
|
||||
//
|
||||
// Source: http://en.wikipedia.org/wiki/Character_encodings_in_HTML#Illegal_characters
|
||||
|
||||
/** @param {number} code */
|
||||
function validate_code(code) {
|
||||
// line feed becomes generic whitespace
|
||||
if (code === 10) {
|
||||
return 32;
|
||||
}
|
||||
|
||||
// ASCII range. (Why someone would use HTML entities for ASCII characters I don't know, but...)
|
||||
if (code < 128) {
|
||||
return code;
|
||||
}
|
||||
|
||||
// code points 128-159 are dealt with leniently by browsers, but they're incorrect. We need
|
||||
// to correct the mistake or we'll end up with missing € signs and so on
|
||||
if (code <= 159) {
|
||||
return windows_1252[code - 128];
|
||||
}
|
||||
|
||||
// basic multilingual plane
|
||||
if (code < 55296) {
|
||||
return code;
|
||||
}
|
||||
|
||||
// UTF-16 surrogate halves
|
||||
if (code <= 57343) {
|
||||
return NUL;
|
||||
}
|
||||
|
||||
// rest of the basic multilingual plane
|
||||
if (code <= 65535) {
|
||||
return code;
|
||||
}
|
||||
|
||||
// supplementary multilingual plane 0x10000 - 0x1ffff
|
||||
if (code >= 65536 && code <= 131071) {
|
||||
return code;
|
||||
}
|
||||
|
||||
// supplementary ideographic plane 0x20000 - 0x2ffff
|
||||
if (code >= 131072 && code <= 196607) {
|
||||
return code;
|
||||
}
|
||||
|
||||
return NUL;
|
||||
}
|
||||
287
node_modules/svelte/src/compiler/phases/2-analyze/css/css-analyze.js
generated
vendored
Normal file
287
node_modules/svelte/src/compiler/phases/2-analyze/css/css-analyze.js
generated
vendored
Normal file
@@ -0,0 +1,287 @@
|
||||
/** @import { ComponentAnalysis } from '../../types.js' */
|
||||
/** @import { AST } from '#compiler' */
|
||||
/** @import { Visitors } from 'zimmerframe' */
|
||||
import { walk } from 'zimmerframe';
|
||||
import * as e from '../../../errors.js';
|
||||
import { is_keyframes_node } from '../../css.js';
|
||||
import { is_global, is_unscoped_pseudo_class } from './utils.js';
|
||||
|
||||
/**
|
||||
* @typedef {Visitors<
|
||||
* AST.CSS.Node,
|
||||
* {
|
||||
* keyframes: string[];
|
||||
* rule: AST.CSS.Rule | null;
|
||||
* }
|
||||
* >} CssVisitors
|
||||
*/
|
||||
|
||||
/**
|
||||
* True if is `:global`
|
||||
* @param {AST.CSS.SimpleSelector} simple_selector
|
||||
*/
|
||||
function is_global_block_selector(simple_selector) {
|
||||
return (
|
||||
simple_selector.type === 'PseudoClassSelector' &&
|
||||
simple_selector.name === 'global' &&
|
||||
simple_selector.args === null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Array<AST.CSS.Node>} path
|
||||
*/
|
||||
function is_in_global_block(path) {
|
||||
return path.some((node) => node.type === 'Rule' && node.metadata.is_global_block);
|
||||
}
|
||||
|
||||
/** @type {CssVisitors} */
|
||||
const css_visitors = {
|
||||
Atrule(node, context) {
|
||||
if (is_keyframes_node(node)) {
|
||||
if (!node.prelude.startsWith('-global-') && !is_in_global_block(context.path)) {
|
||||
context.state.keyframes.push(node.prelude);
|
||||
}
|
||||
}
|
||||
|
||||
context.next();
|
||||
},
|
||||
ComplexSelector(node, context) {
|
||||
context.next(); // analyse relevant selectors first
|
||||
|
||||
{
|
||||
const global = node.children.find(is_global);
|
||||
|
||||
if (global) {
|
||||
const idx = node.children.indexOf(global);
|
||||
|
||||
if (global.selectors[0].args !== null && idx !== 0 && idx !== node.children.length - 1) {
|
||||
// ensure `:global(...)` is not used in the middle of a selector (but multiple `global(...)` in sequence are ok)
|
||||
for (let i = idx + 1; i < node.children.length; i++) {
|
||||
if (!is_global(node.children[i])) {
|
||||
e.css_global_invalid_placement(global.selectors[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ensure `:global(...)` do not lead to invalid css after `:global()` is removed
|
||||
for (const relative_selector of node.children) {
|
||||
for (let i = 0; i < relative_selector.selectors.length; i++) {
|
||||
const selector = relative_selector.selectors[i];
|
||||
|
||||
if (selector.type === 'PseudoClassSelector' && selector.name === 'global') {
|
||||
const child = selector.args?.children[0].children[0];
|
||||
// ensure `:global(element)` to be at the first position in a compound selector
|
||||
if (child?.selectors[0].type === 'TypeSelector' && i !== 0) {
|
||||
e.css_global_invalid_selector_list(selector);
|
||||
}
|
||||
|
||||
// ensure `:global(.class)` is not followed by a type selector, eg: `:global(.class)element`
|
||||
if (relative_selector.selectors[i + 1]?.type === 'TypeSelector') {
|
||||
e.css_type_selector_invalid_placement(relative_selector.selectors[i + 1]);
|
||||
}
|
||||
|
||||
// ensure `:global(...)`contains a single selector
|
||||
// (standalone :global() with multiple selectors is OK)
|
||||
if (
|
||||
selector.args !== null &&
|
||||
selector.args.children.length > 1 &&
|
||||
(node.children.length > 1 || relative_selector.selectors.length > 1)
|
||||
) {
|
||||
e.css_global_invalid_selector(selector);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
node.metadata.rule = context.state.rule;
|
||||
|
||||
node.metadata.used ||= node.children.every(
|
||||
({ metadata }) => metadata.is_global || metadata.is_global_like
|
||||
);
|
||||
|
||||
if (
|
||||
node.metadata.rule?.metadata.parent_rule &&
|
||||
node.children[0]?.selectors[0]?.type === 'NestingSelector'
|
||||
) {
|
||||
const first = node.children[0]?.selectors[1];
|
||||
const no_nesting_scope =
|
||||
first?.type !== 'PseudoClassSelector' || is_unscoped_pseudo_class(first);
|
||||
const parent_is_global = node.metadata.rule.metadata.parent_rule.prelude.children.some(
|
||||
(child) => child.children.length === 1 && child.children[0].metadata.is_global
|
||||
);
|
||||
// mark `&:hover` in `:global(.foo) { &:hover { color: green }}` as used
|
||||
if (no_nesting_scope && parent_is_global) {
|
||||
node.metadata.used = true;
|
||||
}
|
||||
}
|
||||
},
|
||||
RelativeSelector(node, context) {
|
||||
const parent = /** @type {AST.CSS.ComplexSelector} */ (context.path.at(-1));
|
||||
|
||||
if (
|
||||
node.combinator != null &&
|
||||
!context.state.rule?.metadata.parent_rule &&
|
||||
parent.children[0] === node &&
|
||||
context.path.at(-3)?.type !== 'PseudoClassSelector'
|
||||
) {
|
||||
e.css_selector_invalid(node.combinator);
|
||||
}
|
||||
|
||||
node.metadata.is_global = node.selectors.length >= 1 && is_global(node);
|
||||
|
||||
if (node.selectors.length === 1) {
|
||||
const first = node.selectors[0];
|
||||
node.metadata.is_global_like ||=
|
||||
(first.type === 'PseudoClassSelector' && first.name === 'host') ||
|
||||
(first.type === 'PseudoElementSelector' &&
|
||||
[
|
||||
'view-transition',
|
||||
'view-transition-group',
|
||||
'view-transition-old',
|
||||
'view-transition-new',
|
||||
'view-transition-image-pair'
|
||||
].includes(first.name));
|
||||
}
|
||||
|
||||
node.metadata.is_global_like ||=
|
||||
node.selectors.some(
|
||||
(child) => child.type === 'PseudoClassSelector' && child.name === 'root'
|
||||
) &&
|
||||
// :root.y:has(.x) is not a global selector because while .y is unscoped, .x inside `:has(...)` should be scoped
|
||||
!node.selectors.some((child) => child.type === 'PseudoClassSelector' && child.name === 'has');
|
||||
|
||||
if (node.metadata.is_global_like || node.metadata.is_global) {
|
||||
// So that nested selectors like `:root:not(.x)` are not marked as unused
|
||||
for (const child of node.selectors) {
|
||||
walk(/** @type {AST.CSS.Node} */ (child), null, {
|
||||
ComplexSelector(node, context) {
|
||||
node.metadata.used = true;
|
||||
context.next();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
context.next();
|
||||
},
|
||||
Rule(node, context) {
|
||||
node.metadata.parent_rule = context.state.rule;
|
||||
|
||||
node.metadata.is_global_block = node.prelude.children.some((selector) => {
|
||||
let is_global_block = false;
|
||||
|
||||
for (const child of selector.children) {
|
||||
const idx = child.selectors.findIndex(is_global_block_selector);
|
||||
|
||||
if (is_global_block) {
|
||||
// All selectors after :global are unscoped
|
||||
child.metadata.is_global_like = true;
|
||||
}
|
||||
|
||||
if (idx !== -1) {
|
||||
is_global_block = true;
|
||||
for (let i = idx + 1; i < child.selectors.length; i++) {
|
||||
walk(/** @type {AST.CSS.Node} */ (child.selectors[i]), null, {
|
||||
ComplexSelector(node) {
|
||||
node.metadata.used = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return is_global_block;
|
||||
});
|
||||
|
||||
if (node.metadata.is_global_block) {
|
||||
if (node.prelude.children.length > 1) {
|
||||
e.css_global_block_invalid_list(node.prelude);
|
||||
}
|
||||
|
||||
const complex_selector = node.prelude.children[0];
|
||||
const global_selector = complex_selector.children.find((r, selector_idx) => {
|
||||
const idx = r.selectors.findIndex(is_global_block_selector);
|
||||
if (idx === 0) {
|
||||
if (r.selectors.length > 1 && selector_idx === 0 && node.metadata.parent_rule === null) {
|
||||
e.css_global_block_invalid_modifier_start(r.selectors[1]);
|
||||
}
|
||||
return true;
|
||||
} else if (idx !== -1) {
|
||||
e.css_global_block_invalid_modifier(r.selectors[idx]);
|
||||
}
|
||||
});
|
||||
|
||||
if (!global_selector) {
|
||||
throw new Error('Internal error: global block without :global selector');
|
||||
}
|
||||
|
||||
if (global_selector.combinator && global_selector.combinator.name !== ' ') {
|
||||
e.css_global_block_invalid_combinator(global_selector, global_selector.combinator.name);
|
||||
}
|
||||
|
||||
const declaration = node.block.children.find((child) => child.type === 'Declaration');
|
||||
|
||||
if (
|
||||
declaration &&
|
||||
// :global { color: red; } is invalid, but foo :global { color: red; } is valid
|
||||
node.prelude.children.length === 1 &&
|
||||
node.prelude.children[0].children.length === 1 &&
|
||||
node.prelude.children[0].children[0].selectors.length === 1
|
||||
) {
|
||||
e.css_global_block_invalid_declaration(declaration);
|
||||
}
|
||||
}
|
||||
|
||||
context.next({
|
||||
...context.state,
|
||||
rule: node
|
||||
});
|
||||
|
||||
node.metadata.has_local_selectors = node.prelude.children.some((selector) => {
|
||||
return selector.children.some(
|
||||
({ metadata }) => !metadata.is_global && !metadata.is_global_like
|
||||
);
|
||||
});
|
||||
},
|
||||
NestingSelector(node, context) {
|
||||
const rule = /** @type {AST.CSS.Rule} */ (context.state.rule);
|
||||
const parent_rule = rule.metadata.parent_rule;
|
||||
|
||||
if (!parent_rule) {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/CSS/Nesting_selector#using_outside_nested_rule
|
||||
const children = rule.prelude.children;
|
||||
const selectors = children[0].children[0].selectors;
|
||||
if (
|
||||
children.length > 1 ||
|
||||
selectors.length > 1 ||
|
||||
selectors[0].type !== 'PseudoClassSelector' ||
|
||||
selectors[0].name !== 'global' ||
|
||||
selectors[0].args?.children[0]?.children[0].selectors[0] !== node
|
||||
) {
|
||||
e.css_nesting_selector_invalid_placement(node);
|
||||
}
|
||||
} else if (
|
||||
// :global { &.foo { ... } } is invalid
|
||||
parent_rule.metadata.is_global_block &&
|
||||
!parent_rule.metadata.parent_rule &&
|
||||
parent_rule.prelude.children[0].children.length === 1 &&
|
||||
parent_rule.prelude.children[0].children[0].selectors.length === 1
|
||||
) {
|
||||
e.css_global_block_invalid_modifier_start(node);
|
||||
}
|
||||
|
||||
context.next();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {AST.CSS.StyleSheet} stylesheet
|
||||
* @param {ComponentAnalysis} analysis
|
||||
*/
|
||||
export function analyze_css(stylesheet, analysis) {
|
||||
walk(stylesheet, { keyframes: analysis.css.keyframes, rule: null }, css_visitors);
|
||||
}
|
||||
1068
node_modules/svelte/src/compiler/phases/2-analyze/css/css-prune.js
generated
vendored
Normal file
1068
node_modules/svelte/src/compiler/phases/2-analyze/css/css-prune.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
47
node_modules/svelte/src/compiler/phases/2-analyze/css/css-warn.js
generated
vendored
Normal file
47
node_modules/svelte/src/compiler/phases/2-analyze/css/css-warn.js
generated
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
/** @import { Visitors } from 'zimmerframe' */
|
||||
/** @import { AST } from '#compiler' */
|
||||
import { walk } from 'zimmerframe';
|
||||
import * as w from '../../../warnings.js';
|
||||
import { is_keyframes_node } from '../../css.js';
|
||||
|
||||
/**
|
||||
* @param {AST.CSS.StyleSheet} stylesheet
|
||||
*/
|
||||
export function warn_unused(stylesheet) {
|
||||
walk(stylesheet, { stylesheet }, visitors);
|
||||
}
|
||||
|
||||
/** @type {Visitors<AST.CSS.Node, { stylesheet: AST.CSS.StyleSheet }>} */
|
||||
const visitors = {
|
||||
Atrule(node, context) {
|
||||
if (!is_keyframes_node(node)) {
|
||||
context.next();
|
||||
}
|
||||
},
|
||||
PseudoClassSelector(node, context) {
|
||||
if (node.name === 'is' || node.name === 'where') {
|
||||
context.next();
|
||||
}
|
||||
},
|
||||
ComplexSelector(node, context) {
|
||||
if (
|
||||
!node.metadata.used &&
|
||||
// prevent double-marking of `.unused:is(.unused)`
|
||||
(context.path.at(-2)?.type !== 'PseudoClassSelector' ||
|
||||
/** @type {AST.CSS.ComplexSelector} */ (context.path.at(-4))?.metadata.used)
|
||||
) {
|
||||
const content = context.state.stylesheet.content;
|
||||
const text = content.styles.substring(node.start - content.start, node.end - content.start);
|
||||
w.css_unused_selector(node, text);
|
||||
}
|
||||
|
||||
context.next();
|
||||
},
|
||||
Rule(node, context) {
|
||||
if (node.metadata.is_global_block) {
|
||||
context.visit(node.prelude);
|
||||
} else {
|
||||
context.next();
|
||||
}
|
||||
}
|
||||
};
|
||||
177
node_modules/svelte/src/compiler/phases/2-analyze/css/utils.js
generated
vendored
Normal file
177
node_modules/svelte/src/compiler/phases/2-analyze/css/utils.js
generated
vendored
Normal file
@@ -0,0 +1,177 @@
|
||||
/** @import { AST } from '#compiler' */
|
||||
/** @import { Node } from 'estree' */
|
||||
const UNKNOWN = {};
|
||||
|
||||
/**
|
||||
* @param {Node} node
|
||||
* @param {boolean} is_class
|
||||
* @param {Set<any>} set
|
||||
* @param {boolean} is_nested
|
||||
*/
|
||||
function gather_possible_values(node, is_class, set, is_nested = false) {
|
||||
if (set.has(UNKNOWN)) {
|
||||
// no point traversing any further
|
||||
return;
|
||||
}
|
||||
|
||||
if (node.type === 'Literal') {
|
||||
set.add(String(node.value));
|
||||
} else if (node.type === 'ConditionalExpression') {
|
||||
gather_possible_values(node.consequent, is_class, set, is_nested);
|
||||
gather_possible_values(node.alternate, is_class, set, is_nested);
|
||||
} else if (node.type === 'LogicalExpression') {
|
||||
if (node.operator === '&&') {
|
||||
// && is a special case, because the only way the left
|
||||
// hand value can be included is if it's falsy. this is
|
||||
// a bit of extra work but it's worth it because
|
||||
// `class={[condition && 'blah']}` is common,
|
||||
// and we don't want to deopt on `condition`
|
||||
const left = new Set();
|
||||
gather_possible_values(node.left, is_class, left, is_nested);
|
||||
|
||||
if (left.has(UNKNOWN)) {
|
||||
// add all non-nullish falsy values, unless this is a `class` attribute that
|
||||
// will be processed by cslx, in which case falsy values are removed, unless
|
||||
// they're not inside an array/object (TODO 6.0 remove that last part)
|
||||
if (!is_class || !is_nested) {
|
||||
set.add('');
|
||||
set.add(false);
|
||||
set.add(NaN);
|
||||
set.add(0); // -0 and 0n are also falsy, but stringify to '0'
|
||||
}
|
||||
} else {
|
||||
for (const value of left) {
|
||||
if (!value && value != undefined && (!is_class || !is_nested)) {
|
||||
set.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gather_possible_values(node.right, is_class, set, is_nested);
|
||||
} else {
|
||||
gather_possible_values(node.left, is_class, set, is_nested);
|
||||
gather_possible_values(node.right, is_class, set, is_nested);
|
||||
}
|
||||
} else if (is_class && node.type === 'ArrayExpression') {
|
||||
for (const entry of node.elements) {
|
||||
if (entry) {
|
||||
gather_possible_values(entry, is_class, set, true);
|
||||
}
|
||||
}
|
||||
} else if (is_class && node.type === 'ObjectExpression') {
|
||||
for (const property of node.properties) {
|
||||
if (
|
||||
property.type === 'Property' &&
|
||||
!property.computed &&
|
||||
(property.key.type === 'Identifier' || property.key.type === 'Literal')
|
||||
) {
|
||||
set.add(
|
||||
property.key.type === 'Identifier' ? property.key.name : String(property.key.value)
|
||||
);
|
||||
} else {
|
||||
set.add(UNKNOWN);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
set.add(UNKNOWN);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {AST.Text | AST.ExpressionTag} chunk
|
||||
* @param {boolean} is_class
|
||||
* @returns {string[] | null}
|
||||
*/
|
||||
export function get_possible_values(chunk, is_class) {
|
||||
const values = new Set();
|
||||
|
||||
if (chunk.type === 'Text') {
|
||||
values.add(chunk.data);
|
||||
} else {
|
||||
gather_possible_values(chunk.expression, is_class, values);
|
||||
}
|
||||
|
||||
if (values.has(UNKNOWN)) return null;
|
||||
return [...values].map((value) => String(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all parent rules; root is last
|
||||
* @param {AST.CSS.Rule | null} rule
|
||||
*/
|
||||
export function get_parent_rules(rule) {
|
||||
const rules = [];
|
||||
|
||||
while (rule) {
|
||||
rules.push(rule);
|
||||
rule = rule.metadata.parent_rule;
|
||||
}
|
||||
|
||||
return rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* True if is `:global(...)` or `:global` and no pseudo class that is scoped.
|
||||
* @param {AST.CSS.RelativeSelector} relative_selector
|
||||
* @returns {relative_selector is AST.CSS.RelativeSelector & { selectors: [AST.CSS.PseudoClassSelector, ...Array<AST.CSS.PseudoClassSelector | AST.CSS.PseudoElementSelector>] }}
|
||||
*/
|
||||
export function is_global(relative_selector) {
|
||||
const first = relative_selector.selectors[0];
|
||||
|
||||
return (
|
||||
first.type === 'PseudoClassSelector' &&
|
||||
first.name === 'global' &&
|
||||
(first.args === null ||
|
||||
// Only these two selector types keep the whole selector global, because e.g.
|
||||
// :global(button).x means that the selector is still scoped because of the .x
|
||||
relative_selector.selectors.every(
|
||||
(selector) =>
|
||||
is_unscoped_pseudo_class(selector) || selector.type === 'PseudoElementSelector'
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* `true` if is a pseudo class that cannot be or is not scoped
|
||||
* @param {AST.CSS.SimpleSelector} selector
|
||||
*/
|
||||
export function is_unscoped_pseudo_class(selector) {
|
||||
return (
|
||||
selector.type === 'PseudoClassSelector' &&
|
||||
// These make the selector scoped
|
||||
((selector.name !== 'has' &&
|
||||
selector.name !== 'is' &&
|
||||
selector.name !== 'where' &&
|
||||
// Not is special because we want to scope as specific as possible, but because :not
|
||||
// inverses the result, we want to leave the unscoped, too. The exception is more than
|
||||
// one selector in the :not (.e.g :not(.x .y)), then .x and .y should be scoped
|
||||
(selector.name !== 'not' ||
|
||||
selector.args === null ||
|
||||
selector.args.children.every((c) => c.children.length === 1))) ||
|
||||
// selectors with has/is/where/not can also be global if all their children are global
|
||||
selector.args === null ||
|
||||
selector.args.children.every((c) => c.children.every((r) => is_global(r))))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* True if is `:global(...)` or `:global`, irrespective of whether or not there are any pseudo classes that are scoped.
|
||||
* Difference to `is_global`: `:global(x):has(y)` is `true` for `is_outer_global` but `false` for `is_global`.
|
||||
* @param {AST.CSS.RelativeSelector} relative_selector
|
||||
* @returns {relative_selector is AST.CSS.RelativeSelector & { selectors: [AST.CSS.PseudoClassSelector, ...Array<AST.CSS.PseudoClassSelector | AST.CSS.PseudoElementSelector>] }}
|
||||
*/
|
||||
export function is_outer_global(relative_selector) {
|
||||
const first = relative_selector.selectors[0];
|
||||
|
||||
return (
|
||||
first.type === 'PseudoClassSelector' &&
|
||||
first.name === 'global' &&
|
||||
(first.args === null ||
|
||||
// Only these two selector types can keep the whole selector global, because e.g.
|
||||
// :global(button).x means that the selector is still scoped because of the .x
|
||||
relative_selector.selectors.every(
|
||||
(selector) =>
|
||||
selector.type === 'PseudoClassSelector' || selector.type === 'PseudoElementSelector'
|
||||
))
|
||||
);
|
||||
}
|
||||
890
node_modules/svelte/src/compiler/phases/2-analyze/index.js
generated
vendored
Normal file
890
node_modules/svelte/src/compiler/phases/2-analyze/index.js
generated
vendored
Normal file
@@ -0,0 +1,890 @@
|
||||
/** @import { Expression, Node, Program } from 'estree' */
|
||||
/** @import { Binding, AST, ValidatedCompileOptions, ValidatedModuleCompileOptions } from '#compiler' */
|
||||
/** @import { AnalysisState, Visitors } from './types' */
|
||||
/** @import { Analysis, ComponentAnalysis, Js, ReactiveStatement, Template } from '../types' */
|
||||
import { walk } from 'zimmerframe';
|
||||
import * as e from '../../errors.js';
|
||||
import * as w from '../../warnings.js';
|
||||
import { extract_identifiers, is_text_attribute } from '../../utils/ast.js';
|
||||
import * as b from '../../utils/builders.js';
|
||||
import { Scope, ScopeRoot, create_scopes, get_rune, set_scope } from '../scope.js';
|
||||
import check_graph_for_cycles from './utils/check_graph_for_cycles.js';
|
||||
import { create_attribute, is_custom_element_node } from '../nodes.js';
|
||||
import { analyze_css } from './css/css-analyze.js';
|
||||
import { prune } from './css/css-prune.js';
|
||||
import { hash, is_rune } from '../../../utils.js';
|
||||
import { warn_unused } from './css/css-warn.js';
|
||||
import { extract_svelte_ignore } from '../../utils/extract_svelte_ignore.js';
|
||||
import { ignore_map, ignore_stack, pop_ignore, push_ignore } from '../../state.js';
|
||||
import { ArrowFunctionExpression } from './visitors/ArrowFunctionExpression.js';
|
||||
import { AssignmentExpression } from './visitors/AssignmentExpression.js';
|
||||
import { Attribute } from './visitors/Attribute.js';
|
||||
import { AwaitBlock } from './visitors/AwaitBlock.js';
|
||||
import { BindDirective } from './visitors/BindDirective.js';
|
||||
import { CallExpression } from './visitors/CallExpression.js';
|
||||
import { ClassBody } from './visitors/ClassBody.js';
|
||||
import { ClassDeclaration } from './visitors/ClassDeclaration.js';
|
||||
import { ClassDirective } from './visitors/ClassDirective.js';
|
||||
import { Component } from './visitors/Component.js';
|
||||
import { ConstTag } from './visitors/ConstTag.js';
|
||||
import { DebugTag } from './visitors/DebugTag.js';
|
||||
import { EachBlock } from './visitors/EachBlock.js';
|
||||
import { ExportDefaultDeclaration } from './visitors/ExportDefaultDeclaration.js';
|
||||
import { ExportNamedDeclaration } from './visitors/ExportNamedDeclaration.js';
|
||||
import { ExportSpecifier } from './visitors/ExportSpecifier.js';
|
||||
import { ExpressionStatement } from './visitors/ExpressionStatement.js';
|
||||
import { ExpressionTag } from './visitors/ExpressionTag.js';
|
||||
import { FunctionDeclaration } from './visitors/FunctionDeclaration.js';
|
||||
import { FunctionExpression } from './visitors/FunctionExpression.js';
|
||||
import { HtmlTag } from './visitors/HtmlTag.js';
|
||||
import { Identifier } from './visitors/Identifier.js';
|
||||
import { IfBlock } from './visitors/IfBlock.js';
|
||||
import { ImportDeclaration } from './visitors/ImportDeclaration.js';
|
||||
import { KeyBlock } from './visitors/KeyBlock.js';
|
||||
import { LabeledStatement } from './visitors/LabeledStatement.js';
|
||||
import { LetDirective } from './visitors/LetDirective.js';
|
||||
import { MemberExpression } from './visitors/MemberExpression.js';
|
||||
import { NewExpression } from './visitors/NewExpression.js';
|
||||
import { OnDirective } from './visitors/OnDirective.js';
|
||||
import { RegularElement } from './visitors/RegularElement.js';
|
||||
import { RenderTag } from './visitors/RenderTag.js';
|
||||
import { SlotElement } from './visitors/SlotElement.js';
|
||||
import { SnippetBlock } from './visitors/SnippetBlock.js';
|
||||
import { SpreadAttribute } from './visitors/SpreadAttribute.js';
|
||||
import { SpreadElement } from './visitors/SpreadElement.js';
|
||||
import { StyleDirective } from './visitors/StyleDirective.js';
|
||||
import { SvelteBody } from './visitors/SvelteBody.js';
|
||||
import { SvelteComponent } from './visitors/SvelteComponent.js';
|
||||
import { SvelteDocument } from './visitors/SvelteDocument.js';
|
||||
import { SvelteElement } from './visitors/SvelteElement.js';
|
||||
import { SvelteFragment } from './visitors/SvelteFragment.js';
|
||||
import { SvelteHead } from './visitors/SvelteHead.js';
|
||||
import { SvelteSelf } from './visitors/SvelteSelf.js';
|
||||
import { SvelteWindow } from './visitors/SvelteWindow.js';
|
||||
import { SvelteBoundary } from './visitors/SvelteBoundary.js';
|
||||
import { TaggedTemplateExpression } from './visitors/TaggedTemplateExpression.js';
|
||||
import { Text } from './visitors/Text.js';
|
||||
import { TitleElement } from './visitors/TitleElement.js';
|
||||
import { TransitionDirective } from './visitors/TransitionDirective.js';
|
||||
import { UpdateExpression } from './visitors/UpdateExpression.js';
|
||||
import { UseDirective } from './visitors/UseDirective.js';
|
||||
import { VariableDeclarator } from './visitors/VariableDeclarator.js';
|
||||
import is_reference from 'is-reference';
|
||||
import { mark_subtree_dynamic } from './visitors/shared/fragment.js';
|
||||
|
||||
/**
|
||||
* @type {Visitors}
|
||||
*/
|
||||
const visitors = {
|
||||
_(node, { state, next, path }) {
|
||||
const parent = path.at(-1);
|
||||
|
||||
/** @type {string[]} */
|
||||
const ignores = [];
|
||||
|
||||
if (parent?.type === 'Fragment' && node.type !== 'Comment' && node.type !== 'Text') {
|
||||
const idx = parent.nodes.indexOf(/** @type {any} */ (node));
|
||||
|
||||
for (let i = idx - 1; i >= 0; i--) {
|
||||
const prev = parent.nodes[i];
|
||||
|
||||
if (prev.type === 'Comment') {
|
||||
ignores.push(
|
||||
...extract_svelte_ignore(
|
||||
prev.start + 4 /* '<!--'.length */,
|
||||
prev.data,
|
||||
state.analysis.runes
|
||||
)
|
||||
);
|
||||
} else if (prev.type !== 'Text') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const comments = /** @type {any} */ (node).leadingComments;
|
||||
|
||||
if (comments) {
|
||||
for (const comment of comments) {
|
||||
ignores.push(
|
||||
...extract_svelte_ignore(
|
||||
comment.start + 2 /* '//'.length */,
|
||||
comment.value,
|
||||
state.analysis.runes
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ignores.length > 0) {
|
||||
push_ignore(ignores);
|
||||
}
|
||||
|
||||
ignore_map.set(node, structuredClone(ignore_stack));
|
||||
|
||||
const scope = state.scopes.get(node);
|
||||
next(scope !== undefined && scope !== state.scope ? { ...state, scope } : state);
|
||||
|
||||
if (ignores.length > 0) {
|
||||
pop_ignore();
|
||||
}
|
||||
},
|
||||
ArrowFunctionExpression,
|
||||
AssignmentExpression,
|
||||
Attribute,
|
||||
AwaitBlock,
|
||||
BindDirective,
|
||||
CallExpression,
|
||||
ClassBody,
|
||||
ClassDeclaration,
|
||||
ClassDirective,
|
||||
Component,
|
||||
ConstTag,
|
||||
DebugTag,
|
||||
EachBlock,
|
||||
ExportDefaultDeclaration,
|
||||
ExportNamedDeclaration,
|
||||
ExportSpecifier,
|
||||
ExpressionStatement,
|
||||
ExpressionTag,
|
||||
FunctionDeclaration,
|
||||
FunctionExpression,
|
||||
HtmlTag,
|
||||
Identifier,
|
||||
IfBlock,
|
||||
ImportDeclaration,
|
||||
KeyBlock,
|
||||
LabeledStatement,
|
||||
LetDirective,
|
||||
MemberExpression,
|
||||
NewExpression,
|
||||
OnDirective,
|
||||
RegularElement,
|
||||
RenderTag,
|
||||
SlotElement,
|
||||
SnippetBlock,
|
||||
SpreadAttribute,
|
||||
SpreadElement,
|
||||
StyleDirective,
|
||||
SvelteBody,
|
||||
SvelteComponent,
|
||||
SvelteDocument,
|
||||
SvelteElement,
|
||||
SvelteFragment,
|
||||
SvelteHead,
|
||||
SvelteSelf,
|
||||
SvelteWindow,
|
||||
SvelteBoundary,
|
||||
TaggedTemplateExpression,
|
||||
Text,
|
||||
TransitionDirective,
|
||||
TitleElement,
|
||||
UpdateExpression,
|
||||
UseDirective,
|
||||
VariableDeclarator
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {AST.Script | null} script
|
||||
* @param {ScopeRoot} root
|
||||
* @param {boolean} allow_reactive_declarations
|
||||
* @param {Scope | null} parent
|
||||
* @returns {Js}
|
||||
*/
|
||||
function js(script, root, allow_reactive_declarations, parent) {
|
||||
/** @type {Program} */
|
||||
const ast = script?.content ?? {
|
||||
type: 'Program',
|
||||
sourceType: 'module',
|
||||
start: -1,
|
||||
end: -1,
|
||||
body: []
|
||||
};
|
||||
|
||||
const { scope, scopes } = create_scopes(ast, root, allow_reactive_declarations, parent);
|
||||
|
||||
return { ast, scope, scopes };
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} filename
|
||||
*/
|
||||
function get_component_name(filename) {
|
||||
const parts = filename.split(/[/\\]/);
|
||||
const basename = /** @type {string} */ (parts.pop());
|
||||
const last_dir = /** @type {string} */ (parts.at(-1));
|
||||
let name = basename.replace('.svelte', '');
|
||||
if (name === 'index' && last_dir && last_dir !== 'src') {
|
||||
name = last_dir;
|
||||
}
|
||||
return name[0].toUpperCase() + name.slice(1);
|
||||
}
|
||||
|
||||
const RESERVED = ['$$props', '$$restProps', '$$slots'];
|
||||
|
||||
/**
|
||||
* @param {Program} ast
|
||||
* @param {ValidatedModuleCompileOptions} options
|
||||
* @returns {Analysis}
|
||||
*/
|
||||
export function analyze_module(ast, options) {
|
||||
const { scope, scopes } = create_scopes(ast, new ScopeRoot(), false, null);
|
||||
|
||||
for (const [name, references] of scope.references) {
|
||||
if (name[0] !== '$' || RESERVED.includes(name)) continue;
|
||||
if (name === '$' || name[1] === '$') {
|
||||
e.global_reference_invalid(references[0].node, name);
|
||||
}
|
||||
|
||||
const binding = scope.get(name.slice(1));
|
||||
|
||||
if (binding !== null && !is_rune(name)) {
|
||||
e.store_invalid_subscription_module(references[0].node);
|
||||
}
|
||||
}
|
||||
|
||||
/** @type {Analysis} */
|
||||
const analysis = {
|
||||
module: { ast, scope, scopes },
|
||||
name: options.filename,
|
||||
accessors: false,
|
||||
runes: true,
|
||||
immutable: true,
|
||||
tracing: false
|
||||
};
|
||||
|
||||
walk(
|
||||
/** @type {Node} */ (ast),
|
||||
{
|
||||
scope,
|
||||
scopes,
|
||||
// @ts-expect-error TODO
|
||||
analysis
|
||||
},
|
||||
visitors
|
||||
);
|
||||
|
||||
return analysis;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {AST.Root} root
|
||||
* @param {string} source
|
||||
* @param {ValidatedCompileOptions} options
|
||||
* @returns {ComponentAnalysis}
|
||||
*/
|
||||
export function analyze_component(root, source, options) {
|
||||
const scope_root = new ScopeRoot();
|
||||
|
||||
const module = js(root.module, scope_root, false, null);
|
||||
const instance = js(root.instance, scope_root, true, module.scope);
|
||||
|
||||
const { scope, scopes } = create_scopes(root.fragment, scope_root, false, instance.scope);
|
||||
|
||||
/** @type {Template} */
|
||||
const template = { ast: root.fragment, scope, scopes };
|
||||
|
||||
let synthetic_stores_legacy_check = [];
|
||||
|
||||
// create synthetic bindings for store subscriptions
|
||||
for (const [name, references] of module.scope.references) {
|
||||
if (name[0] !== '$' || RESERVED.includes(name)) continue;
|
||||
if (name === '$' || name[1] === '$') {
|
||||
e.global_reference_invalid(references[0].node, name);
|
||||
}
|
||||
|
||||
const store_name = name.slice(1);
|
||||
const declaration = instance.scope.get(store_name);
|
||||
const init = /** @type {Node | undefined} */ (declaration?.initial);
|
||||
|
||||
// If we're not in legacy mode through the compiler option, assume the user
|
||||
// is referencing a rune and not a global store.
|
||||
if (
|
||||
options.runes === false ||
|
||||
!is_rune(name) ||
|
||||
(declaration !== null &&
|
||||
// const state = $state(0) is valid
|
||||
(get_rune(init, instance.scope) === null ||
|
||||
// rune-line names received as props are valid too (but we have to protect against $props as store)
|
||||
(store_name !== 'props' && get_rune(init, instance.scope) === '$props')) &&
|
||||
// allow `import { derived } from 'svelte/store'` in the same file as `const x = $derived(..)` because one is not a subscription to the other
|
||||
!(
|
||||
name === '$derived' &&
|
||||
declaration.initial?.type === 'ImportDeclaration' &&
|
||||
declaration.initial.source.value === 'svelte/store'
|
||||
))
|
||||
) {
|
||||
let is_nested_store_subscription_node = undefined;
|
||||
search: for (const reference of references) {
|
||||
for (let i = reference.path.length - 1; i >= 0; i--) {
|
||||
const scope =
|
||||
scopes.get(reference.path[i]) ||
|
||||
module.scopes.get(reference.path[i]) ||
|
||||
instance.scopes.get(reference.path[i]);
|
||||
if (scope) {
|
||||
const owner = scope?.owner(store_name);
|
||||
if (!!owner && owner !== module.scope && owner !== instance.scope) {
|
||||
is_nested_store_subscription_node = reference.node;
|
||||
break search;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (is_nested_store_subscription_node) {
|
||||
e.store_invalid_scoped_subscription(is_nested_store_subscription_node);
|
||||
}
|
||||
|
||||
if (options.runes !== false) {
|
||||
if (declaration === null && /[a-z]/.test(store_name[0])) {
|
||||
e.global_reference_invalid(references[0].node, name);
|
||||
} else if (declaration !== null && is_rune(name)) {
|
||||
for (const { node, path } of references) {
|
||||
if (path.at(-1)?.type === 'CallExpression') {
|
||||
w.store_rune_conflict(node, store_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (module.ast) {
|
||||
for (const { node, path } of references) {
|
||||
// if the reference is inside module, error. this is a bit hacky but it works
|
||||
if (
|
||||
/** @type {number} */ (node.start) > /** @type {number} */ (module.ast.start) &&
|
||||
/** @type {number} */ (node.end) < /** @type {number} */ (module.ast.end) &&
|
||||
// const state = $state(0) is valid
|
||||
get_rune(/** @type {Node} */ (path.at(-1)), module.scope) === null
|
||||
) {
|
||||
e.store_invalid_subscription(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// we push to the array because at this moment in time we can't be sure if we are in legacy
|
||||
// mode yet because we are still changing the module scope
|
||||
synthetic_stores_legacy_check.push(() => {
|
||||
// if we are creating a synthetic binding for a let declaration we should also declare
|
||||
// the declaration as state in case it's reassigned and we are not in runes mode (the function will
|
||||
// not be called if we are not in runes mode, that's why there's no !runes check here)
|
||||
if (
|
||||
declaration !== null &&
|
||||
declaration.kind === 'normal' &&
|
||||
declaration.declaration_kind === 'let' &&
|
||||
declaration.reassigned
|
||||
) {
|
||||
declaration.kind = 'state';
|
||||
}
|
||||
});
|
||||
|
||||
const binding = instance.scope.declare(b.id(name), 'store_sub', 'synthetic');
|
||||
binding.references = references;
|
||||
instance.scope.references.set(name, references);
|
||||
module.scope.references.delete(name);
|
||||
}
|
||||
}
|
||||
|
||||
const component_name = get_component_name(options.filename);
|
||||
|
||||
const runes = options.runes ?? Array.from(module.scope.references.keys()).some(is_rune);
|
||||
|
||||
if (!runes) {
|
||||
for (let check of synthetic_stores_legacy_check) {
|
||||
check();
|
||||
}
|
||||
}
|
||||
|
||||
if (runes && root.module) {
|
||||
const context = root.module.attributes.find((attribute) => attribute.name === 'context');
|
||||
if (context) {
|
||||
w.script_context_deprecated(context);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO remove all the ?? stuff, we don't need it now that we're validating the config
|
||||
/** @type {ComponentAnalysis} */
|
||||
const analysis = {
|
||||
name: module.scope.generate(options.name ?? component_name),
|
||||
root: scope_root,
|
||||
module,
|
||||
instance,
|
||||
template,
|
||||
elements: [],
|
||||
runes,
|
||||
tracing: false,
|
||||
immutable: runes || options.immutable,
|
||||
exports: [],
|
||||
uses_props: false,
|
||||
uses_rest_props: false,
|
||||
uses_slots: false,
|
||||
uses_component_bindings: false,
|
||||
uses_render_tags: false,
|
||||
needs_context: false,
|
||||
needs_props: false,
|
||||
event_directive_node: null,
|
||||
uses_event_attributes: false,
|
||||
custom_element: options.customElementOptions ?? options.customElement,
|
||||
inject_styles: options.css === 'injected' || options.customElement,
|
||||
accessors: options.customElement
|
||||
? true
|
||||
: (runes ? false : !!options.accessors) ||
|
||||
// because $set method needs accessors
|
||||
options.compatibility?.componentApi === 4,
|
||||
reactive_statements: new Map(),
|
||||
binding_groups: new Map(),
|
||||
slot_names: new Map(),
|
||||
css: {
|
||||
ast: root.css,
|
||||
hash: root.css
|
||||
? options.cssHash({
|
||||
css: root.css.content.styles,
|
||||
filename: options.filename,
|
||||
name: component_name,
|
||||
hash
|
||||
})
|
||||
: '',
|
||||
keyframes: []
|
||||
},
|
||||
source,
|
||||
undefined_exports: new Map(),
|
||||
snippet_renderers: new Map(),
|
||||
snippets: new Set()
|
||||
};
|
||||
|
||||
if (!runes) {
|
||||
// every exported `let` or `var` declaration becomes a prop, everything else becomes an export
|
||||
for (const node of instance.ast.body) {
|
||||
if (node.type !== 'ExportNamedDeclaration') continue;
|
||||
|
||||
analysis.needs_props = true;
|
||||
|
||||
if (node.declaration) {
|
||||
if (
|
||||
node.declaration.type === 'FunctionDeclaration' ||
|
||||
node.declaration.type === 'ClassDeclaration'
|
||||
) {
|
||||
analysis.exports.push({
|
||||
name: /** @type {import('estree').Identifier} */ (node.declaration.id).name,
|
||||
alias: null
|
||||
});
|
||||
} else if (node.declaration.type === 'VariableDeclaration') {
|
||||
if (node.declaration.kind === 'const') {
|
||||
for (const declarator of node.declaration.declarations) {
|
||||
for (const node of extract_identifiers(declarator.id)) {
|
||||
analysis.exports.push({ name: node.name, alias: null });
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const declarator of node.declaration.declarations) {
|
||||
for (const id of extract_identifiers(declarator.id)) {
|
||||
const binding = /** @type {Binding} */ (instance.scope.get(id.name));
|
||||
binding.kind = 'bindable_prop';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const specifier of node.specifiers) {
|
||||
if (specifier.local.type !== 'Identifier' || specifier.exported.type !== 'Identifier') {
|
||||
continue;
|
||||
}
|
||||
|
||||
const binding = instance.scope.get(specifier.local.name);
|
||||
|
||||
if (
|
||||
binding &&
|
||||
(binding.declaration_kind === 'var' || binding.declaration_kind === 'let')
|
||||
) {
|
||||
binding.kind = 'bindable_prop';
|
||||
|
||||
if (specifier.exported.name !== specifier.local.name) {
|
||||
binding.prop_alias = specifier.exported.name;
|
||||
}
|
||||
} else {
|
||||
analysis.exports.push({ name: specifier.local.name, alias: specifier.exported.name });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if reassigned/mutated bindings are referenced in `$:` blocks
|
||||
// or the template, turn them into state
|
||||
for (const binding of instance.scope.declarations.values()) {
|
||||
if (binding.kind !== 'normal') continue;
|
||||
|
||||
for (const { node, path } of binding.references) {
|
||||
if (node === binding.node) continue;
|
||||
|
||||
if (binding.updated) {
|
||||
if (
|
||||
path[path.length - 1].type === 'StyleDirective' ||
|
||||
path.some((node) => node.type === 'Fragment') ||
|
||||
(path[1].type === 'LabeledStatement' && path[1].label.name === '$')
|
||||
) {
|
||||
binding.kind = 'state';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// more legacy nonsense: if an `each` binding is reassigned/mutated,
|
||||
// treat the expression as being mutated as well
|
||||
walk(/** @type {AST.SvelteNode} */ (template.ast), null, {
|
||||
EachBlock(node) {
|
||||
const scope = /** @type {Scope} */ (template.scopes.get(node));
|
||||
|
||||
for (const binding of scope.declarations.values()) {
|
||||
if (binding.updated) {
|
||||
const state = { scope: /** @type {Scope} */ (scope.parent), scopes: template.scopes };
|
||||
|
||||
walk(node.expression, state, {
|
||||
// @ts-expect-error
|
||||
_: set_scope,
|
||||
Identifier(node, context) {
|
||||
const parent = /** @type {Expression} */ (context.path.at(-1));
|
||||
|
||||
if (is_reference(node, parent)) {
|
||||
const binding = context.state.scope.get(node.name);
|
||||
|
||||
if (
|
||||
binding &&
|
||||
binding.kind === 'normal' &&
|
||||
binding.declaration_kind !== 'import'
|
||||
) {
|
||||
binding.kind = 'state';
|
||||
binding.mutated = binding.updated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (root.options) {
|
||||
for (const attribute of root.options.attributes) {
|
||||
if (attribute.name === 'accessors' && analysis.runes) {
|
||||
w.options_deprecated_accessors(attribute);
|
||||
}
|
||||
|
||||
if (attribute.name === 'customElement' && !options.customElement) {
|
||||
w.options_missing_custom_element(attribute);
|
||||
}
|
||||
|
||||
if (attribute.name === 'immutable' && analysis.runes) {
|
||||
w.options_deprecated_immutable(attribute);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (analysis.runes) {
|
||||
const props_refs = module.scope.references.get('$$props');
|
||||
if (props_refs) {
|
||||
e.legacy_props_invalid(props_refs[0].node);
|
||||
}
|
||||
|
||||
const rest_props_refs = module.scope.references.get('$$restProps');
|
||||
if (rest_props_refs) {
|
||||
e.legacy_rest_props_invalid(rest_props_refs[0].node);
|
||||
}
|
||||
|
||||
for (const { ast, scope, scopes } of [module, instance, template]) {
|
||||
/** @type {AnalysisState} */
|
||||
const state = {
|
||||
scope,
|
||||
scopes,
|
||||
analysis,
|
||||
options,
|
||||
ast_type: ast === instance.ast ? 'instance' : ast === template.ast ? 'template' : 'module',
|
||||
parent_element: null,
|
||||
has_props_rune: false,
|
||||
component_slots: new Set(),
|
||||
expression: null,
|
||||
private_derived_state: [],
|
||||
function_depth: scope.function_depth,
|
||||
instance_scope: instance.scope,
|
||||
reactive_statement: null,
|
||||
reactive_statements: new Map()
|
||||
};
|
||||
|
||||
walk(/** @type {AST.SvelteNode} */ (ast), state, visitors);
|
||||
}
|
||||
|
||||
// warn on any nonstate declarations that are a) reassigned and b) referenced in the template
|
||||
for (const scope of [module.scope, instance.scope]) {
|
||||
outer: for (const [name, binding] of scope.declarations) {
|
||||
if (binding.kind === 'normal' && binding.reassigned) {
|
||||
inner: for (const { path } of binding.references) {
|
||||
if (path[0].type !== 'Fragment') continue;
|
||||
for (let i = 1; i < path.length; i += 1) {
|
||||
const type = path[i].type;
|
||||
if (
|
||||
type === 'FunctionDeclaration' ||
|
||||
type === 'FunctionExpression' ||
|
||||
type === 'ArrowFunctionExpression'
|
||||
) {
|
||||
continue inner;
|
||||
}
|
||||
// bind:this doesn't need to be a state reference if it will never change
|
||||
if (
|
||||
type === 'BindDirective' &&
|
||||
/** @type {AST.BindDirective} */ (path[i]).name === 'this'
|
||||
) {
|
||||
for (let j = i - 1; j >= 0; j -= 1) {
|
||||
const type = path[j].type;
|
||||
if (
|
||||
type === 'IfBlock' ||
|
||||
type === 'EachBlock' ||
|
||||
type === 'AwaitBlock' ||
|
||||
type === 'KeyBlock'
|
||||
) {
|
||||
w.non_reactive_update(binding.node, name);
|
||||
continue outer;
|
||||
}
|
||||
}
|
||||
continue inner;
|
||||
}
|
||||
}
|
||||
|
||||
w.non_reactive_update(binding.node, name);
|
||||
continue outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
instance.scope.declare(b.id('$$props'), 'rest_prop', 'synthetic');
|
||||
instance.scope.declare(b.id('$$restProps'), 'rest_prop', 'synthetic');
|
||||
|
||||
for (const { ast, scope, scopes } of [module, instance, template]) {
|
||||
/** @type {AnalysisState} */
|
||||
const state = {
|
||||
scope,
|
||||
scopes,
|
||||
analysis,
|
||||
options,
|
||||
parent_element: null,
|
||||
has_props_rune: false,
|
||||
ast_type: ast === instance.ast ? 'instance' : ast === template.ast ? 'template' : 'module',
|
||||
instance_scope: instance.scope,
|
||||
reactive_statement: null,
|
||||
reactive_statements: analysis.reactive_statements,
|
||||
component_slots: new Set(),
|
||||
expression: null,
|
||||
private_derived_state: [],
|
||||
function_depth: scope.function_depth
|
||||
};
|
||||
|
||||
walk(/** @type {AST.SvelteNode} */ (ast), state, visitors);
|
||||
}
|
||||
|
||||
for (const [name, binding] of instance.scope.declarations) {
|
||||
if (
|
||||
(binding.kind === 'prop' || binding.kind === 'bindable_prop') &&
|
||||
binding.node.name !== '$$props'
|
||||
) {
|
||||
const references = binding.references.filter(
|
||||
(r) => r.node !== binding.node && r.path.at(-1)?.type !== 'ExportSpecifier'
|
||||
);
|
||||
if (!references.length && !instance.scope.declarations.has(`$${name}`)) {
|
||||
w.export_let_unused(binding.node, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
analysis.reactive_statements = order_reactive_statements(analysis.reactive_statements);
|
||||
}
|
||||
|
||||
for (const node of analysis.module.ast.body) {
|
||||
if (node.type === 'ExportNamedDeclaration' && node.specifiers !== null && node.source == null) {
|
||||
for (const specifier of node.specifiers) {
|
||||
if (specifier.local.type !== 'Identifier') continue;
|
||||
|
||||
const binding = analysis.module.scope.get(specifier.local.name);
|
||||
if (!binding) e.export_undefined(specifier, specifier.local.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (analysis.event_directive_node && analysis.uses_event_attributes) {
|
||||
e.mixed_event_handler_syntaxes(
|
||||
analysis.event_directive_node,
|
||||
analysis.event_directive_node.name
|
||||
);
|
||||
}
|
||||
|
||||
for (const [node, resolved] of analysis.snippet_renderers) {
|
||||
if (!resolved) {
|
||||
node.metadata.snippets = analysis.snippets;
|
||||
}
|
||||
|
||||
for (const snippet of node.metadata.snippets) {
|
||||
snippet.metadata.sites.add(node);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
analysis.uses_render_tags &&
|
||||
(analysis.uses_slots || (!analysis.custom_element && analysis.slot_names.size > 0))
|
||||
) {
|
||||
const pos = analysis.slot_names.values().next().value ?? analysis.source.indexOf('$$slot');
|
||||
e.slot_snippet_conflict(pos);
|
||||
}
|
||||
|
||||
if (analysis.css.ast) {
|
||||
analyze_css(analysis.css.ast, analysis);
|
||||
|
||||
// mark nodes as scoped/unused/empty etc
|
||||
for (const node of analysis.elements) {
|
||||
prune(analysis.css.ast, node);
|
||||
}
|
||||
|
||||
const { comment } = analysis.css.ast.content;
|
||||
const should_ignore_unused =
|
||||
comment &&
|
||||
extract_svelte_ignore(comment.start, comment.data, analysis.runes).includes(
|
||||
'css_unused_selector'
|
||||
);
|
||||
|
||||
if (!should_ignore_unused) {
|
||||
warn_unused(analysis.css.ast);
|
||||
}
|
||||
|
||||
outer: for (const node of analysis.elements) {
|
||||
if (node.metadata.scoped) {
|
||||
// Dynamic elements in dom mode always use spread for attributes and therefore shouldn't have a class attribute added to them
|
||||
// TODO this happens during the analysis phase, which shouldn't know anything about client vs server
|
||||
if (node.type === 'SvelteElement' && options.generate === 'client') continue;
|
||||
|
||||
/** @type {AST.Attribute | undefined} */
|
||||
let class_attribute = undefined;
|
||||
|
||||
for (const attribute of node.attributes) {
|
||||
if (attribute.type === 'SpreadAttribute') {
|
||||
// The spread method appends the hash to the end of the class attribute on its own
|
||||
continue outer;
|
||||
}
|
||||
|
||||
if (attribute.type !== 'Attribute') continue;
|
||||
if (attribute.name.toLowerCase() !== 'class') continue;
|
||||
// The dynamic class method appends the hash to the end of the class attribute on its own
|
||||
if (attribute.metadata.needs_clsx) continue outer;
|
||||
|
||||
class_attribute = attribute;
|
||||
}
|
||||
|
||||
if (class_attribute && class_attribute.value !== true) {
|
||||
if (is_text_attribute(class_attribute)) {
|
||||
class_attribute.value[0].data += ` ${analysis.css.hash}`;
|
||||
} else {
|
||||
/** @type {AST.Text} */
|
||||
const css_text = {
|
||||
type: 'Text',
|
||||
data: ` ${analysis.css.hash}`,
|
||||
raw: ` ${analysis.css.hash}`,
|
||||
start: -1,
|
||||
end: -1
|
||||
};
|
||||
|
||||
if (Array.isArray(class_attribute.value)) {
|
||||
class_attribute.value.push(css_text);
|
||||
} else {
|
||||
class_attribute.value = [class_attribute.value, css_text];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
node.attributes.push(
|
||||
create_attribute('class', -1, -1, [
|
||||
{
|
||||
type: 'Text',
|
||||
data: analysis.css.hash,
|
||||
raw: analysis.css.hash,
|
||||
start: -1,
|
||||
end: -1
|
||||
}
|
||||
])
|
||||
);
|
||||
if (is_custom_element_node(node) && node.attributes.length === 1) {
|
||||
mark_subtree_dynamic(node.metadata.path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO
|
||||
// analysis.stylesheet.warn_on_unused_selectors(analysis);
|
||||
|
||||
return analysis;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Map<import('estree').LabeledStatement, ReactiveStatement>} unsorted_reactive_declarations
|
||||
*/
|
||||
function order_reactive_statements(unsorted_reactive_declarations) {
|
||||
/** @typedef {[import('estree').LabeledStatement, ReactiveStatement]} Tuple */
|
||||
|
||||
/** @type {Map<string, Array<Tuple>>} */
|
||||
const lookup = new Map();
|
||||
|
||||
for (const [node, declaration] of unsorted_reactive_declarations) {
|
||||
for (const binding of declaration.assignments) {
|
||||
const statements = lookup.get(binding.node.name) ?? [];
|
||||
statements.push([node, declaration]);
|
||||
lookup.set(binding.node.name, statements);
|
||||
}
|
||||
}
|
||||
|
||||
/** @type {Array<[string, string]>} */
|
||||
const edges = [];
|
||||
|
||||
for (const [, { assignments, dependencies }] of unsorted_reactive_declarations) {
|
||||
for (const assignment of assignments) {
|
||||
for (const dependency of dependencies) {
|
||||
if (!assignments.has(dependency)) {
|
||||
edges.push([assignment.node.name, dependency.node.name]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const cycle = check_graph_for_cycles(edges);
|
||||
if (cycle?.length) {
|
||||
const declaration = /** @type {Tuple[]} */ (lookup.get(cycle[0]))[0];
|
||||
e.reactive_declaration_cycle(declaration[0], cycle.join(' → '));
|
||||
}
|
||||
|
||||
// We use a map and take advantage of the fact that the spec says insertion order is preserved when iterating
|
||||
/** @type {Map<import('estree').LabeledStatement, ReactiveStatement>} */
|
||||
const reactive_declarations = new Map();
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('estree').LabeledStatement} node
|
||||
* @param {ReactiveStatement} declaration
|
||||
* @returns
|
||||
*/
|
||||
const add_declaration = (node, declaration) => {
|
||||
if ([...reactive_declarations.values()].includes(declaration)) return;
|
||||
|
||||
for (const binding of declaration.dependencies) {
|
||||
if (declaration.assignments.has(binding)) continue;
|
||||
for (const [node, earlier] of lookup.get(binding.node.name) ?? []) {
|
||||
add_declaration(node, earlier);
|
||||
}
|
||||
}
|
||||
|
||||
reactive_declarations.set(node, declaration);
|
||||
};
|
||||
|
||||
for (const [node, declaration] of unsorted_reactive_declarations) {
|
||||
add_declaration(node, declaration);
|
||||
}
|
||||
|
||||
return reactive_declarations;
|
||||
}
|
||||
39
node_modules/svelte/src/compiler/phases/2-analyze/types.d.ts
generated
vendored
Normal file
39
node_modules/svelte/src/compiler/phases/2-analyze/types.d.ts
generated
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
import type { Scope } from '../scope.js';
|
||||
import type { ComponentAnalysis, ReactiveStatement } from '../types.js';
|
||||
import type { AST, ExpressionMetadata, ValidatedCompileOptions } from '#compiler';
|
||||
import type { LabeledStatement } from 'estree';
|
||||
|
||||
export interface AnalysisState {
|
||||
scope: Scope;
|
||||
scopes: Map<AST.SvelteNode, Scope>;
|
||||
analysis: ComponentAnalysis;
|
||||
options: ValidatedCompileOptions;
|
||||
ast_type: 'instance' | 'template' | 'module';
|
||||
/**
|
||||
* Tag name of the parent element. `null` if the parent is `svelte:element`, `#snippet`, a component or the root.
|
||||
* Parent doesn't necessarily mean direct path predecessor because there could be `#each`, `#if` etc in-between.
|
||||
*/
|
||||
parent_element: string | null;
|
||||
has_props_rune: boolean;
|
||||
/** Which slots the current parent component has */
|
||||
component_slots: Set<string>;
|
||||
/** Information about the current expression/directive/block value */
|
||||
expression: ExpressionMetadata | null;
|
||||
private_derived_state: string[];
|
||||
function_depth: number;
|
||||
|
||||
// legacy stuff
|
||||
instance_scope: Scope;
|
||||
reactive_statement: null | ReactiveStatement;
|
||||
reactive_statements: Map<LabeledStatement, ReactiveStatement>;
|
||||
}
|
||||
|
||||
export type Context<State extends AnalysisState = AnalysisState> = import('zimmerframe').Context<
|
||||
AST.SvelteNode,
|
||||
State
|
||||
>;
|
||||
|
||||
export type Visitors<State extends AnalysisState = AnalysisState> = import('zimmerframe').Visitors<
|
||||
AST.SvelteNode,
|
||||
State
|
||||
>;
|
||||
46
node_modules/svelte/src/compiler/phases/2-analyze/utils/check_graph_for_cycles.js
generated
vendored
Normal file
46
node_modules/svelte/src/compiler/phases/2-analyze/utils/check_graph_for_cycles.js
generated
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* @template T
|
||||
* @param {Array<[T, T]>} edges
|
||||
* @returns {Array<T>|undefined}
|
||||
*/
|
||||
export default function check_graph_for_cycles(edges) {
|
||||
/** @type {Map<T, T[]>} */
|
||||
const graph = edges.reduce((g, edge) => {
|
||||
const [u, v] = edge;
|
||||
if (!g.has(u)) g.set(u, []);
|
||||
if (!g.has(v)) g.set(v, []);
|
||||
g.get(u).push(v);
|
||||
return g;
|
||||
}, new Map());
|
||||
|
||||
const visited = new Set();
|
||||
const on_stack = new Set();
|
||||
/** @type {Array<Array<T>>} */
|
||||
const cycles = [];
|
||||
|
||||
/**
|
||||
* @param {T} v
|
||||
*/
|
||||
function visit(v) {
|
||||
visited.add(v);
|
||||
on_stack.add(v);
|
||||
|
||||
graph.get(v)?.forEach((w) => {
|
||||
if (!visited.has(w)) {
|
||||
visit(w);
|
||||
} else if (on_stack.has(w)) {
|
||||
cycles.push([...on_stack, w]);
|
||||
}
|
||||
});
|
||||
|
||||
on_stack.delete(v);
|
||||
}
|
||||
|
||||
graph.forEach((_, v) => {
|
||||
if (!visited.has(v)) {
|
||||
visit(v);
|
||||
}
|
||||
});
|
||||
|
||||
return cycles[0];
|
||||
}
|
||||
11
node_modules/svelte/src/compiler/phases/2-analyze/visitors/ArrowFunctionExpression.js
generated
vendored
Normal file
11
node_modules/svelte/src/compiler/phases/2-analyze/visitors/ArrowFunctionExpression.js
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
/** @import { ArrowFunctionExpression } from 'estree' */
|
||||
/** @import { Context } from '../types' */
|
||||
import { visit_function } from './shared/function.js';
|
||||
|
||||
/**
|
||||
* @param {ArrowFunctionExpression} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function ArrowFunctionExpression(node, context) {
|
||||
visit_function(node, context);
|
||||
}
|
||||
27
node_modules/svelte/src/compiler/phases/2-analyze/visitors/AssignmentExpression.js
generated
vendored
Normal file
27
node_modules/svelte/src/compiler/phases/2-analyze/visitors/AssignmentExpression.js
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
/** @import { AssignmentExpression } from 'estree' */
|
||||
/** @import { Context } from '../types' */
|
||||
import { extract_identifiers, object } from '../../../utils/ast.js';
|
||||
import { validate_assignment } from './shared/utils.js';
|
||||
|
||||
/**
|
||||
* @param {AssignmentExpression} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function AssignmentExpression(node, context) {
|
||||
validate_assignment(node, node.left, context.state);
|
||||
|
||||
if (context.state.reactive_statement) {
|
||||
const id = node.left.type === 'MemberExpression' ? object(node.left) : node.left;
|
||||
if (id !== null) {
|
||||
for (const id of extract_identifiers(node.left)) {
|
||||
const binding = context.state.scope.get(id.name);
|
||||
|
||||
if (binding) {
|
||||
context.state.reactive_statement.assignments.add(binding);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context.next();
|
||||
}
|
||||
243
node_modules/svelte/src/compiler/phases/2-analyze/visitors/Attribute.js
generated
vendored
Normal file
243
node_modules/svelte/src/compiler/phases/2-analyze/visitors/Attribute.js
generated
vendored
Normal file
@@ -0,0 +1,243 @@
|
||||
/** @import { ArrowFunctionExpression, Expression, FunctionDeclaration, FunctionExpression } from 'estree' */
|
||||
/** @import { AST, DelegatedEvent } from '#compiler' */
|
||||
/** @import { Context } from '../types' */
|
||||
import { cannot_be_set_statically, is_capture_event, is_delegated } from '../../../../utils.js';
|
||||
import {
|
||||
get_attribute_chunks,
|
||||
get_attribute_expression,
|
||||
is_event_attribute
|
||||
} from '../../../utils/ast.js';
|
||||
import { mark_subtree_dynamic } from './shared/fragment.js';
|
||||
|
||||
/**
|
||||
* @param {AST.Attribute} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function Attribute(node, context) {
|
||||
context.next();
|
||||
|
||||
const parent = /** @type {AST.SvelteNode} */ (context.path.at(-1));
|
||||
|
||||
if (parent.type === 'RegularElement') {
|
||||
// special case <option value="" />
|
||||
if (node.name === 'value' && parent.name === 'option') {
|
||||
mark_subtree_dynamic(context.path);
|
||||
}
|
||||
|
||||
// special case <img loading="lazy" />
|
||||
if (node.name === 'loading' && parent.name === 'img') {
|
||||
mark_subtree_dynamic(context.path);
|
||||
}
|
||||
}
|
||||
|
||||
if (is_event_attribute(node)) {
|
||||
mark_subtree_dynamic(context.path);
|
||||
}
|
||||
|
||||
if (cannot_be_set_statically(node.name)) {
|
||||
mark_subtree_dynamic(context.path);
|
||||
}
|
||||
|
||||
// class={[...]} or class={{...}} or `class={x}` need clsx to resolve the classes
|
||||
if (
|
||||
node.name === 'class' &&
|
||||
!Array.isArray(node.value) &&
|
||||
node.value !== true &&
|
||||
node.value.expression.type !== 'Literal' &&
|
||||
node.value.expression.type !== 'TemplateLiteral' &&
|
||||
node.value.expression.type !== 'BinaryExpression'
|
||||
) {
|
||||
mark_subtree_dynamic(context.path);
|
||||
node.metadata.needs_clsx = true;
|
||||
}
|
||||
|
||||
if (node.value !== true) {
|
||||
for (const chunk of get_attribute_chunks(node.value)) {
|
||||
if (chunk.type !== 'ExpressionTag') continue;
|
||||
|
||||
if (
|
||||
chunk.expression.type === 'FunctionExpression' ||
|
||||
chunk.expression.type === 'ArrowFunctionExpression'
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_event_attribute(node)) {
|
||||
const parent = context.path.at(-1);
|
||||
if (parent?.type === 'RegularElement' || parent?.type === 'SvelteElement') {
|
||||
context.state.analysis.uses_event_attributes = true;
|
||||
}
|
||||
|
||||
const expression = get_attribute_expression(node);
|
||||
const delegated_event = get_delegated_event(node.name.slice(2), expression, context);
|
||||
|
||||
if (delegated_event !== null) {
|
||||
if (delegated_event.hoisted) {
|
||||
delegated_event.function.metadata.hoisted = true;
|
||||
}
|
||||
|
||||
node.metadata.delegated = delegated_event;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @type {DelegatedEvent} */
|
||||
const unhoisted = { hoisted: false };
|
||||
|
||||
/**
|
||||
* Checks if given event attribute can be delegated/hoisted and returns the corresponding info if so
|
||||
* @param {string} event_name
|
||||
* @param {Expression | null} handler
|
||||
* @param {Context} context
|
||||
* @returns {null | DelegatedEvent}
|
||||
*/
|
||||
function get_delegated_event(event_name, handler, context) {
|
||||
// Handle delegated event handlers. Bail out if not a delegated event.
|
||||
if (!handler || !is_delegated(event_name)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// If we are not working with a RegularElement, then bail out.
|
||||
const element = context.path.at(-1);
|
||||
if (element?.type !== 'RegularElement') {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @type {FunctionExpression | FunctionDeclaration | ArrowFunctionExpression | null} */
|
||||
let target_function = null;
|
||||
let binding = null;
|
||||
|
||||
if (element.metadata.has_spread) {
|
||||
// event attribute becomes part of the dynamic spread array
|
||||
return unhoisted;
|
||||
}
|
||||
|
||||
if (handler.type === 'ArrowFunctionExpression' || handler.type === 'FunctionExpression') {
|
||||
target_function = handler;
|
||||
} else if (handler.type === 'Identifier') {
|
||||
binding = context.state.scope.get(handler.name);
|
||||
|
||||
if (context.state.analysis.module.scope.references.has(handler.name)) {
|
||||
// If a binding with the same name is referenced in the module scope (even if not declared there), bail out
|
||||
return unhoisted;
|
||||
}
|
||||
|
||||
if (binding != null) {
|
||||
for (const { path } of binding.references) {
|
||||
const parent = path.at(-1);
|
||||
if (parent === undefined) return unhoisted;
|
||||
|
||||
const grandparent = path.at(-2);
|
||||
|
||||
/** @type {AST.RegularElement | null} */
|
||||
let element = null;
|
||||
/** @type {string | null} */
|
||||
let event_name = null;
|
||||
if (parent.type === 'OnDirective') {
|
||||
element = /** @type {AST.RegularElement} */ (grandparent);
|
||||
event_name = parent.name;
|
||||
} else if (
|
||||
parent.type === 'ExpressionTag' &&
|
||||
grandparent?.type === 'Attribute' &&
|
||||
is_event_attribute(grandparent)
|
||||
) {
|
||||
element = /** @type {AST.RegularElement} */ (path.at(-3));
|
||||
const attribute = /** @type {AST.Attribute} */ (grandparent);
|
||||
event_name = get_attribute_event_name(attribute.name);
|
||||
}
|
||||
|
||||
if (element && event_name) {
|
||||
if (
|
||||
element.type !== 'RegularElement' ||
|
||||
element.metadata.has_spread ||
|
||||
!is_delegated(event_name)
|
||||
) {
|
||||
return unhoisted;
|
||||
}
|
||||
} else if (parent.type !== 'FunctionDeclaration' && parent.type !== 'VariableDeclarator') {
|
||||
return unhoisted;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the binding is exported, bail out
|
||||
if (context.state.analysis.exports.find((node) => node.name === handler.name)) {
|
||||
return unhoisted;
|
||||
}
|
||||
|
||||
if (binding !== null && binding.initial !== null && !binding.updated && !binding.is_called) {
|
||||
const binding_type = binding.initial.type;
|
||||
|
||||
if (
|
||||
binding_type === 'ArrowFunctionExpression' ||
|
||||
binding_type === 'FunctionDeclaration' ||
|
||||
binding_type === 'FunctionExpression'
|
||||
) {
|
||||
target_function = binding.initial;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we can't find a function, or the function has multiple parameters, bail out
|
||||
if (target_function == null || target_function.params.length > 1) {
|
||||
return unhoisted;
|
||||
}
|
||||
|
||||
const visited_references = new Set();
|
||||
const scope = target_function.metadata.scope;
|
||||
for (const [reference] of scope.references) {
|
||||
// Bail out if the arguments keyword is used or $host is referenced
|
||||
if (reference === 'arguments' || reference === '$host') return unhoisted;
|
||||
// Bail out if references a store subscription
|
||||
if (scope.get(`$${reference}`)?.kind === 'store_sub') return unhoisted;
|
||||
|
||||
const binding = scope.get(reference);
|
||||
const local_binding = context.state.scope.get(reference);
|
||||
|
||||
// If we are referencing a binding that is shadowed in another scope then bail out.
|
||||
if (local_binding !== null && binding !== null && local_binding.node !== binding.node) {
|
||||
return unhoisted;
|
||||
}
|
||||
|
||||
// If we have multiple references to the same store using $ prefix, bail out.
|
||||
if (
|
||||
binding !== null &&
|
||||
binding.kind === 'store_sub' &&
|
||||
visited_references.has(reference.slice(1))
|
||||
) {
|
||||
return unhoisted;
|
||||
}
|
||||
|
||||
// If we reference the index within an each block, then bail out.
|
||||
if (binding !== null && binding.initial?.type === 'EachBlock') return unhoisted;
|
||||
|
||||
if (
|
||||
binding !== null &&
|
||||
// Bail out if the the binding is a rest param
|
||||
(binding.declaration_kind === 'rest_param' ||
|
||||
// Bail out if we reference anything from the EachBlock (for now) that mutates in non-runes mode,
|
||||
(((!context.state.analysis.runes && binding.kind === 'each') ||
|
||||
// or any normal not reactive bindings that are mutated.
|
||||
binding.kind === 'normal') &&
|
||||
binding.updated))
|
||||
) {
|
||||
return unhoisted;
|
||||
}
|
||||
visited_references.add(reference);
|
||||
}
|
||||
|
||||
return { hoisted: true, function: target_function };
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} event_name
|
||||
*/
|
||||
function get_attribute_event_name(event_name) {
|
||||
event_name = event_name.slice(2);
|
||||
if (is_capture_event(event_name)) {
|
||||
event_name = event_name.slice(0, -7);
|
||||
}
|
||||
return event_name;
|
||||
}
|
||||
45
node_modules/svelte/src/compiler/phases/2-analyze/visitors/AwaitBlock.js
generated
vendored
Normal file
45
node_modules/svelte/src/compiler/phases/2-analyze/visitors/AwaitBlock.js
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
/** @import { AST } from '#compiler' */
|
||||
/** @import { Context } from '../types' */
|
||||
import { validate_block_not_empty, validate_opening_tag } from './shared/utils.js';
|
||||
import * as e from '../../../errors.js';
|
||||
import { mark_subtree_dynamic } from './shared/fragment.js';
|
||||
|
||||
/**
|
||||
* @param {AST.AwaitBlock} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function AwaitBlock(node, context) {
|
||||
validate_block_not_empty(node.pending, context);
|
||||
validate_block_not_empty(node.then, context);
|
||||
validate_block_not_empty(node.catch, context);
|
||||
|
||||
if (context.state.analysis.runes) {
|
||||
validate_opening_tag(node, context.state, '#');
|
||||
|
||||
if (node.value) {
|
||||
const start = /** @type {number} */ (node.value.start);
|
||||
const match = context.state.analysis.source
|
||||
.substring(start - 10, start)
|
||||
.match(/{(\s*):then\s+$/);
|
||||
|
||||
if (match && match[1] !== '') {
|
||||
e.block_unexpected_character({ start: start - 10, end: start }, ':');
|
||||
}
|
||||
}
|
||||
|
||||
if (node.error) {
|
||||
const start = /** @type {number} */ (node.error.start);
|
||||
const match = context.state.analysis.source
|
||||
.substring(start - 10, start)
|
||||
.match(/{(\s*):catch\s+$/);
|
||||
|
||||
if (match && match[1] !== '') {
|
||||
e.block_unexpected_character({ start: start - 10, end: start }, ':');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mark_subtree_dynamic(context.path);
|
||||
|
||||
context.next();
|
||||
}
|
||||
258
node_modules/svelte/src/compiler/phases/2-analyze/visitors/BindDirective.js
generated
vendored
Normal file
258
node_modules/svelte/src/compiler/phases/2-analyze/visitors/BindDirective.js
generated
vendored
Normal file
@@ -0,0 +1,258 @@
|
||||
/** @import { AST } from '#compiler' */
|
||||
/** @import { Context } from '../types' */
|
||||
import {
|
||||
extract_all_identifiers_from_expression,
|
||||
is_text_attribute,
|
||||
object
|
||||
} from '../../../utils/ast.js';
|
||||
import { validate_no_const_assignment } from './shared/utils.js';
|
||||
import * as e from '../../../errors.js';
|
||||
import * as w from '../../../warnings.js';
|
||||
import { binding_properties } from '../../bindings.js';
|
||||
import fuzzymatch from '../../1-parse/utils/fuzzymatch.js';
|
||||
import { is_content_editable_binding, is_svg } from '../../../../utils.js';
|
||||
import { mark_subtree_dynamic } from './shared/fragment.js';
|
||||
|
||||
/**
|
||||
* @param {AST.BindDirective} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function BindDirective(node, context) {
|
||||
const parent = context.path.at(-1);
|
||||
|
||||
if (
|
||||
parent?.type === 'RegularElement' ||
|
||||
parent?.type === 'SvelteElement' ||
|
||||
parent?.type === 'SvelteWindow' ||
|
||||
parent?.type === 'SvelteDocument' ||
|
||||
parent?.type === 'SvelteBody'
|
||||
) {
|
||||
if (node.name in binding_properties) {
|
||||
const property = binding_properties[node.name];
|
||||
if (property.valid_elements && !property.valid_elements.includes(parent.name)) {
|
||||
e.bind_invalid_target(
|
||||
node,
|
||||
node.name,
|
||||
property.valid_elements.map((valid_element) => `<${valid_element}>`).join(', ')
|
||||
);
|
||||
}
|
||||
|
||||
if (property.invalid_elements && property.invalid_elements.includes(parent.name)) {
|
||||
const valid_bindings = Object.entries(binding_properties)
|
||||
.filter(([_, binding_property]) => {
|
||||
return (
|
||||
binding_property.valid_elements?.includes(parent.name) ||
|
||||
(!binding_property.valid_elements &&
|
||||
!binding_property.invalid_elements?.includes(parent.name))
|
||||
);
|
||||
})
|
||||
.map(([property_name]) => property_name)
|
||||
.sort();
|
||||
|
||||
e.bind_invalid_name(
|
||||
node,
|
||||
node.name,
|
||||
`Possible bindings for <${parent.name}> are ${valid_bindings.join(', ')}`
|
||||
);
|
||||
}
|
||||
|
||||
if (parent.name === 'input' && node.name !== 'this') {
|
||||
const type = /** @type {AST.Attribute | undefined} */ (
|
||||
parent.attributes.find((a) => a.type === 'Attribute' && a.name === 'type')
|
||||
);
|
||||
|
||||
if (type && !is_text_attribute(type)) {
|
||||
if (node.name !== 'value' || type.value === true) {
|
||||
e.attribute_invalid_type(type);
|
||||
}
|
||||
} else {
|
||||
if (node.name === 'checked' && type?.value[0].data !== 'checkbox') {
|
||||
e.bind_invalid_target(node, node.name, '<input type="checkbox">');
|
||||
}
|
||||
|
||||
if (node.name === 'files' && type?.value[0].data !== 'file') {
|
||||
e.bind_invalid_target(node, node.name, '<input type="file">');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (parent.name === 'select' && node.name !== 'this') {
|
||||
const multiple = parent.attributes.find(
|
||||
(a) =>
|
||||
a.type === 'Attribute' &&
|
||||
a.name === 'multiple' &&
|
||||
!is_text_attribute(a) &&
|
||||
a.value !== true
|
||||
);
|
||||
|
||||
if (multiple) {
|
||||
e.attribute_invalid_multiple(multiple);
|
||||
}
|
||||
}
|
||||
|
||||
if (node.name === 'offsetWidth' && is_svg(parent.name)) {
|
||||
e.bind_invalid_target(
|
||||
node,
|
||||
node.name,
|
||||
`non-<svg> elements. Use 'clientWidth' for <svg> instead`
|
||||
);
|
||||
}
|
||||
|
||||
if (is_content_editable_binding(node.name)) {
|
||||
const contenteditable = /** @type {AST.Attribute} */ (
|
||||
parent.attributes.find((a) => a.type === 'Attribute' && a.name === 'contenteditable')
|
||||
);
|
||||
|
||||
if (!contenteditable) {
|
||||
e.attribute_contenteditable_missing(node);
|
||||
} else if (!is_text_attribute(contenteditable) && contenteditable.value !== true) {
|
||||
e.attribute_contenteditable_dynamic(contenteditable);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const match = fuzzymatch(node.name, Object.keys(binding_properties));
|
||||
|
||||
if (match) {
|
||||
const property = binding_properties[match];
|
||||
if (!property.valid_elements || property.valid_elements.includes(parent.name)) {
|
||||
e.bind_invalid_name(node, node.name, `Did you mean '${match}'?`);
|
||||
}
|
||||
}
|
||||
|
||||
e.bind_invalid_name(node, node.name);
|
||||
}
|
||||
}
|
||||
|
||||
// When dealing with bind getters/setters skip the specific binding validation
|
||||
// Group bindings aren't supported for getter/setters so we don't need to handle
|
||||
// the metadata
|
||||
if (node.expression.type === 'SequenceExpression') {
|
||||
if (node.name === 'group') {
|
||||
e.bind_group_invalid_expression(node);
|
||||
}
|
||||
|
||||
let i = /** @type {number} */ (node.expression.start);
|
||||
let leading_comments_start = /**@type {any}*/ (node.expression.leadingComments?.at(0))?.start;
|
||||
let leading_comments_end = /**@type {any}*/ (node.expression.leadingComments?.at(-1))?.end;
|
||||
while (context.state.analysis.source[--i] !== '{') {
|
||||
if (
|
||||
context.state.analysis.source[i] === '(' &&
|
||||
// if the parenthesis is in a leading comment we don't need to throw the error
|
||||
!(
|
||||
leading_comments_start &&
|
||||
leading_comments_end &&
|
||||
i <= leading_comments_end &&
|
||||
i >= leading_comments_start
|
||||
)
|
||||
) {
|
||||
e.bind_invalid_parens(node, node.name);
|
||||
}
|
||||
}
|
||||
|
||||
if (node.expression.expressions.length !== 2) {
|
||||
e.bind_invalid_expression(node);
|
||||
}
|
||||
|
||||
mark_subtree_dynamic(context.path);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
validate_no_const_assignment(node, node.expression, context.state.scope, true);
|
||||
|
||||
const assignee = node.expression;
|
||||
const left = object(assignee);
|
||||
|
||||
if (left === null) {
|
||||
e.bind_invalid_expression(node);
|
||||
}
|
||||
|
||||
const binding = context.state.scope.get(left.name);
|
||||
|
||||
if (assignee.type === 'Identifier') {
|
||||
// reassignment
|
||||
if (
|
||||
node.name !== 'this' && // bind:this also works for regular variables
|
||||
(!binding ||
|
||||
(binding.kind !== 'state' &&
|
||||
binding.kind !== 'raw_state' &&
|
||||
binding.kind !== 'prop' &&
|
||||
binding.kind !== 'bindable_prop' &&
|
||||
binding.kind !== 'each' &&
|
||||
binding.kind !== 'store_sub' &&
|
||||
!binding.updated)) // TODO wut?
|
||||
) {
|
||||
e.bind_invalid_value(node.expression);
|
||||
}
|
||||
|
||||
if (context.state.analysis.runes && binding?.kind === 'each') {
|
||||
e.each_item_invalid_assignment(node);
|
||||
}
|
||||
|
||||
if (binding?.kind === 'snippet') {
|
||||
e.snippet_parameter_assignment(node);
|
||||
}
|
||||
}
|
||||
|
||||
if (node.name === 'group') {
|
||||
if (!binding) {
|
||||
throw new Error('Cannot find declaration for bind:group');
|
||||
}
|
||||
|
||||
// Traverse the path upwards and find all EachBlocks who are (indirectly) contributing to bind:group,
|
||||
// i.e. one of their declarations is referenced in the binding. This allows group bindings to work
|
||||
// correctly when referencing a variable declared in an EachBlock by using the index of the each block
|
||||
// entries as keys.
|
||||
const each_blocks = [];
|
||||
const [keypath, expression_ids] = extract_all_identifiers_from_expression(node.expression);
|
||||
let ids = expression_ids;
|
||||
|
||||
let i = context.path.length;
|
||||
while (i--) {
|
||||
const parent = context.path[i];
|
||||
|
||||
if (parent.type === 'EachBlock') {
|
||||
const references = ids.filter((id) => parent.metadata.declarations.has(id.name));
|
||||
|
||||
if (references.length > 0) {
|
||||
parent.metadata.contains_group_binding = true;
|
||||
|
||||
each_blocks.push(parent);
|
||||
ids = ids.filter((id) => !references.includes(id));
|
||||
ids.push(...extract_all_identifiers_from_expression(parent.expression)[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The identifiers that make up the binding expression form they key for the binding group.
|
||||
// If the same identifiers in the same order are used in another bind:group, they will be in the same group.
|
||||
// (there's an edge case where `bind:group={a[i]}` will be in a different group than `bind:group={a[j]}` even when i == j,
|
||||
// but this is a limitation of the current static analysis we do; it also never worked in Svelte 4)
|
||||
const bindings = expression_ids.map((id) => context.state.scope.get(id.name));
|
||||
let group_name;
|
||||
|
||||
outer: for (const [[key, b], group] of context.state.analysis.binding_groups) {
|
||||
if (b.length !== bindings.length || key !== keypath) continue;
|
||||
for (let i = 0; i < bindings.length; i++) {
|
||||
if (bindings[i] !== b[i]) continue outer;
|
||||
}
|
||||
group_name = group;
|
||||
}
|
||||
|
||||
if (!group_name) {
|
||||
group_name = context.state.scope.root.unique('binding_group');
|
||||
context.state.analysis.binding_groups.set([keypath, bindings], group_name);
|
||||
}
|
||||
|
||||
node.metadata = {
|
||||
binding_group_name: group_name,
|
||||
parent_each_blocks: each_blocks
|
||||
};
|
||||
}
|
||||
|
||||
if (binding?.kind === 'each' && binding.metadata?.inside_rest) {
|
||||
w.bind_invalid_each_rest(binding.node, binding.node.name);
|
||||
}
|
||||
|
||||
context.next();
|
||||
}
|
||||
243
node_modules/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js
generated
vendored
Normal file
243
node_modules/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js
generated
vendored
Normal file
@@ -0,0 +1,243 @@
|
||||
/** @import { ArrowFunctionExpression, CallExpression, Expression, FunctionDeclaration, FunctionExpression, Identifier, VariableDeclarator } from 'estree' */
|
||||
/** @import { AST } from '#compiler' */
|
||||
/** @import { Context } from '../types' */
|
||||
import { get_rune } from '../../scope.js';
|
||||
import * as e from '../../../errors.js';
|
||||
import { get_parent, unwrap_optional } from '../../../utils/ast.js';
|
||||
import { is_pure, is_safe_identifier } from './shared/utils.js';
|
||||
import { dev, locate_node, source } from '../../../state.js';
|
||||
import * as b from '../../../utils/builders.js';
|
||||
|
||||
/**
|
||||
* @param {CallExpression} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function CallExpression(node, context) {
|
||||
const parent = /** @type {AST.SvelteNode} */ (get_parent(context.path, -1));
|
||||
|
||||
const rune = get_rune(node, context.state.scope);
|
||||
|
||||
switch (rune) {
|
||||
case null:
|
||||
if (!is_safe_identifier(node.callee, context.state.scope)) {
|
||||
context.state.analysis.needs_context = true;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case '$bindable':
|
||||
if (node.arguments.length > 1) {
|
||||
e.rune_invalid_arguments_length(node, '$bindable', 'zero or one arguments');
|
||||
}
|
||||
|
||||
if (
|
||||
parent.type !== 'AssignmentPattern' ||
|
||||
context.path.at(-3)?.type !== 'ObjectPattern' ||
|
||||
context.path.at(-4)?.type !== 'VariableDeclarator' ||
|
||||
get_rune(
|
||||
/** @type {VariableDeclarator} */ (context.path.at(-4)).init,
|
||||
context.state.scope
|
||||
) !== '$props'
|
||||
) {
|
||||
e.bindable_invalid_location(node);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case '$host':
|
||||
if (node.arguments.length > 0) {
|
||||
e.rune_invalid_arguments(node, '$host');
|
||||
} else if (context.state.ast_type === 'module' || !context.state.analysis.custom_element) {
|
||||
e.host_invalid_placement(node);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case '$props':
|
||||
if (context.state.has_props_rune) {
|
||||
e.props_duplicate(node);
|
||||
}
|
||||
|
||||
context.state.has_props_rune = true;
|
||||
|
||||
if (
|
||||
parent.type !== 'VariableDeclarator' ||
|
||||
context.state.ast_type !== 'instance' ||
|
||||
context.state.scope !== context.state.analysis.instance.scope
|
||||
) {
|
||||
e.props_invalid_placement(node);
|
||||
}
|
||||
|
||||
if (node.arguments.length > 0) {
|
||||
e.rune_invalid_arguments(node, rune);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case '$state':
|
||||
case '$state.raw':
|
||||
case '$derived':
|
||||
case '$derived.by':
|
||||
if (
|
||||
(parent.type !== 'VariableDeclarator' ||
|
||||
get_parent(context.path, -3).type === 'ConstTag') &&
|
||||
!(parent.type === 'PropertyDefinition' && !parent.static && !parent.computed)
|
||||
) {
|
||||
e.state_invalid_placement(node, rune);
|
||||
}
|
||||
|
||||
if ((rune === '$derived' || rune === '$derived.by') && node.arguments.length !== 1) {
|
||||
e.rune_invalid_arguments_length(node, rune, 'exactly one argument');
|
||||
} else if (rune === '$state' && node.arguments.length > 1) {
|
||||
e.rune_invalid_arguments_length(node, rune, 'zero or one arguments');
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case '$effect':
|
||||
case '$effect.pre':
|
||||
if (parent.type !== 'ExpressionStatement') {
|
||||
e.effect_invalid_placement(node);
|
||||
}
|
||||
|
||||
if (node.arguments.length !== 1) {
|
||||
e.rune_invalid_arguments_length(node, rune, 'exactly one argument');
|
||||
}
|
||||
|
||||
// `$effect` needs context because Svelte needs to know whether it should re-run
|
||||
// effects that invalidate themselves, and that's determined by whether we're in runes mode
|
||||
context.state.analysis.needs_context = true;
|
||||
|
||||
break;
|
||||
|
||||
case '$effect.tracking':
|
||||
if (node.arguments.length !== 0) {
|
||||
e.rune_invalid_arguments(node, rune);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case '$effect.root':
|
||||
if (node.arguments.length !== 1) {
|
||||
e.rune_invalid_arguments_length(node, rune, 'exactly one argument');
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case '$inspect':
|
||||
if (node.arguments.length < 1) {
|
||||
e.rune_invalid_arguments_length(node, rune, 'one or more arguments');
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case '$inspect().with':
|
||||
if (node.arguments.length !== 1) {
|
||||
e.rune_invalid_arguments_length(node, rune, 'exactly one argument');
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case '$inspect.trace': {
|
||||
if (node.arguments.length > 1) {
|
||||
e.rune_invalid_arguments_length(node, rune, 'zero or one arguments');
|
||||
}
|
||||
|
||||
const grand_parent = context.path.at(-2);
|
||||
const fn = context.path.at(-3);
|
||||
|
||||
if (
|
||||
parent.type !== 'ExpressionStatement' ||
|
||||
grand_parent?.type !== 'BlockStatement' ||
|
||||
!(
|
||||
fn?.type === 'FunctionDeclaration' ||
|
||||
fn?.type === 'FunctionExpression' ||
|
||||
fn?.type === 'ArrowFunctionExpression'
|
||||
) ||
|
||||
grand_parent.body[0] !== parent
|
||||
) {
|
||||
e.inspect_trace_invalid_placement(node);
|
||||
}
|
||||
|
||||
if (fn.generator) {
|
||||
e.inspect_trace_generator(node);
|
||||
}
|
||||
|
||||
if (dev) {
|
||||
if (node.arguments[0]) {
|
||||
context.state.scope.tracing = b.thunk(/** @type {Expression} */ (node.arguments[0]));
|
||||
} else {
|
||||
const label = get_function_label(context.path.slice(0, -2)) ?? 'trace';
|
||||
const loc = `(${locate_node(fn)})`;
|
||||
|
||||
context.state.scope.tracing = b.thunk(b.literal(label + ' ' + loc));
|
||||
}
|
||||
|
||||
context.state.analysis.tracing = true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case '$state.snapshot':
|
||||
if (node.arguments.length !== 1) {
|
||||
e.rune_invalid_arguments_length(node, rune, 'exactly one argument');
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (node.callee.type === 'Identifier') {
|
||||
const binding = context.state.scope.get(node.callee.name);
|
||||
|
||||
if (binding !== null) {
|
||||
binding.is_called = true;
|
||||
}
|
||||
}
|
||||
|
||||
// `$inspect(foo)` or `$derived(foo) should not trigger the `static-state-reference` warning
|
||||
if (rune === '$inspect' || rune === '$derived') {
|
||||
context.next({ ...context.state, function_depth: context.state.function_depth + 1 });
|
||||
} else {
|
||||
context.next();
|
||||
}
|
||||
|
||||
if (context.state.expression) {
|
||||
// TODO We assume that any dependencies are stateful, which isn't necessarily the case — see
|
||||
// https://github.com/sveltejs/svelte/issues/13266. This check also includes dependencies
|
||||
// outside the call expression itself (e.g. `{blah && pure()}`) resulting in additional
|
||||
// false positives, but for now we accept that trade-off
|
||||
if (!is_pure(node.callee, context) || context.state.expression.dependencies.size > 0) {
|
||||
context.state.expression.has_call = true;
|
||||
context.state.expression.has_state = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {AST.SvelteNode[]} nodes
|
||||
*/
|
||||
function get_function_label(nodes) {
|
||||
const fn = /** @type {FunctionExpression | FunctionDeclaration | ArrowFunctionExpression} */ (
|
||||
nodes.at(-1)
|
||||
);
|
||||
|
||||
if ((fn.type === 'FunctionDeclaration' || fn.type === 'FunctionExpression') && fn.id != null) {
|
||||
return fn.id.name;
|
||||
}
|
||||
|
||||
const parent = nodes.at(-2);
|
||||
if (!parent) return;
|
||||
|
||||
if (parent.type === 'CallExpression') {
|
||||
return source.slice(parent.callee.start, parent.callee.end) + '(...)';
|
||||
}
|
||||
|
||||
if (parent.type === 'Property' && !parent.computed) {
|
||||
return /** @type {Identifier} */ (parent.key).name;
|
||||
}
|
||||
|
||||
if (parent.type === 'VariableDeclarator' && parent.id.type === 'Identifier') {
|
||||
return parent.id.name;
|
||||
}
|
||||
}
|
||||
27
node_modules/svelte/src/compiler/phases/2-analyze/visitors/ClassBody.js
generated
vendored
Normal file
27
node_modules/svelte/src/compiler/phases/2-analyze/visitors/ClassBody.js
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
/** @import { ClassBody } from 'estree' */
|
||||
/** @import { Context } from '../types' */
|
||||
import { get_rune } from '../../scope.js';
|
||||
|
||||
/**
|
||||
* @param {ClassBody} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function ClassBody(node, context) {
|
||||
/** @type {string[]} */
|
||||
const private_derived_state = [];
|
||||
|
||||
for (const definition of node.body) {
|
||||
if (
|
||||
definition.type === 'PropertyDefinition' &&
|
||||
definition.key.type === 'PrivateIdentifier' &&
|
||||
definition.value?.type === 'CallExpression'
|
||||
) {
|
||||
const rune = get_rune(definition.value, context.state.scope);
|
||||
if (rune === '$derived' || rune === '$derived.by') {
|
||||
private_derived_state.push(definition.key.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context.next({ ...context.state, private_derived_state });
|
||||
}
|
||||
25
node_modules/svelte/src/compiler/phases/2-analyze/visitors/ClassDeclaration.js
generated
vendored
Normal file
25
node_modules/svelte/src/compiler/phases/2-analyze/visitors/ClassDeclaration.js
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
/** @import { ClassDeclaration } from 'estree' */
|
||||
/** @import { Context } from '../types' */
|
||||
import * as w from '../../../warnings.js';
|
||||
import { validate_identifier_name } from './shared/utils.js';
|
||||
|
||||
/**
|
||||
* @param {ClassDeclaration} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function ClassDeclaration(node, context) {
|
||||
if (context.state.analysis.runes && node.id !== null) {
|
||||
validate_identifier_name(context.state.scope.get(node.id.name));
|
||||
}
|
||||
|
||||
// In modules, we allow top-level module scope only, in components, we allow the component scope,
|
||||
// which is function_depth of 1. With the exception of `new class` which is also not allowed at
|
||||
// component scope level either.
|
||||
const allowed_depth = context.state.ast_type === 'module' ? 0 : 1;
|
||||
|
||||
if (context.state.scope.function_depth > allowed_depth) {
|
||||
w.perf_avoid_nested_class(node);
|
||||
}
|
||||
|
||||
context.next();
|
||||
}
|
||||
13
node_modules/svelte/src/compiler/phases/2-analyze/visitors/ClassDirective.js
generated
vendored
Normal file
13
node_modules/svelte/src/compiler/phases/2-analyze/visitors/ClassDirective.js
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
/** @import { AST } from '#compiler' */
|
||||
/** @import { Context } from '../types' */
|
||||
|
||||
import { mark_subtree_dynamic } from './shared/fragment.js';
|
||||
|
||||
/**
|
||||
* @param {AST.ClassDirective} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function ClassDirective(node, context) {
|
||||
mark_subtree_dynamic(context.path);
|
||||
context.next({ ...context.state, expression: node.metadata.expression });
|
||||
}
|
||||
20
node_modules/svelte/src/compiler/phases/2-analyze/visitors/Component.js
generated
vendored
Normal file
20
node_modules/svelte/src/compiler/phases/2-analyze/visitors/Component.js
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
/** @import { AST } from '#compiler' */
|
||||
/** @import { Context } from '../types' */
|
||||
import { visit_component } from './shared/component.js';
|
||||
|
||||
/**
|
||||
* @param {AST.Component} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function Component(node, context) {
|
||||
const binding = context.state.scope.get(
|
||||
node.name.includes('.') ? node.name.slice(0, node.name.indexOf('.')) : node.name
|
||||
);
|
||||
|
||||
node.metadata.dynamic =
|
||||
context.state.analysis.runes && // Svelte 4 required you to use svelte:component to switch components
|
||||
binding !== null &&
|
||||
(binding.kind !== 'normal' || node.name.includes('.'));
|
||||
|
||||
visit_component(node, context);
|
||||
}
|
||||
35
node_modules/svelte/src/compiler/phases/2-analyze/visitors/ConstTag.js
generated
vendored
Normal file
35
node_modules/svelte/src/compiler/phases/2-analyze/visitors/ConstTag.js
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
/** @import { AST } from '#compiler' */
|
||||
/** @import { Context } from '../types' */
|
||||
import * as e from '../../../errors.js';
|
||||
import { validate_opening_tag } from './shared/utils.js';
|
||||
|
||||
/**
|
||||
* @param {AST.ConstTag} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function ConstTag(node, context) {
|
||||
if (context.state.analysis.runes) {
|
||||
validate_opening_tag(node, context.state, '@');
|
||||
}
|
||||
|
||||
const parent = context.path.at(-1);
|
||||
const grand_parent = context.path.at(-2);
|
||||
|
||||
if (
|
||||
parent?.type !== 'Fragment' ||
|
||||
(grand_parent?.type !== 'IfBlock' &&
|
||||
grand_parent?.type !== 'SvelteFragment' &&
|
||||
grand_parent?.type !== 'Component' &&
|
||||
grand_parent?.type !== 'SvelteComponent' &&
|
||||
grand_parent?.type !== 'EachBlock' &&
|
||||
grand_parent?.type !== 'AwaitBlock' &&
|
||||
grand_parent?.type !== 'SnippetBlock' &&
|
||||
grand_parent?.type !== 'SvelteBoundary' &&
|
||||
((grand_parent?.type !== 'RegularElement' && grand_parent?.type !== 'SvelteElement') ||
|
||||
!grand_parent.attributes.some((a) => a.type === 'Attribute' && a.name === 'slot')))
|
||||
) {
|
||||
e.const_tag_invalid_placement(node);
|
||||
}
|
||||
|
||||
context.next();
|
||||
}
|
||||
15
node_modules/svelte/src/compiler/phases/2-analyze/visitors/DebugTag.js
generated
vendored
Normal file
15
node_modules/svelte/src/compiler/phases/2-analyze/visitors/DebugTag.js
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
/** @import { AST } from '#compiler' */
|
||||
/** @import { Context } from '../types' */
|
||||
import { validate_opening_tag } from './shared/utils.js';
|
||||
|
||||
/**
|
||||
* @param {AST.DebugTag} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function DebugTag(node, context) {
|
||||
if (context.state.analysis.runes) {
|
||||
validate_opening_tag(node, context.state, '@');
|
||||
}
|
||||
|
||||
context.next();
|
||||
}
|
||||
42
node_modules/svelte/src/compiler/phases/2-analyze/visitors/EachBlock.js
generated
vendored
Normal file
42
node_modules/svelte/src/compiler/phases/2-analyze/visitors/EachBlock.js
generated
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
/** @import { AST } from '#compiler' */
|
||||
/** @import { Context } from '../types' */
|
||||
/** @import { Scope } from '../../scope' */
|
||||
import * as e from '../../../errors.js';
|
||||
import { mark_subtree_dynamic } from './shared/fragment.js';
|
||||
import { validate_block_not_empty, validate_opening_tag } from './shared/utils.js';
|
||||
|
||||
/**
|
||||
* @param {AST.EachBlock} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function EachBlock(node, context) {
|
||||
validate_opening_tag(node, context.state, '#');
|
||||
|
||||
validate_block_not_empty(node.body, context);
|
||||
validate_block_not_empty(node.fallback, context);
|
||||
|
||||
const id = node.context;
|
||||
if (id?.type === 'Identifier' && (id.name === '$state' || id.name === '$derived')) {
|
||||
// TODO weird that this is necessary
|
||||
e.state_invalid_placement(node, id.name);
|
||||
}
|
||||
|
||||
if (node.key) {
|
||||
// treat `{#each items as item, i (i)}` as a normal indexed block, everything else as keyed
|
||||
node.metadata.keyed =
|
||||
node.key.type !== 'Identifier' || !node.index || node.key.name !== node.index;
|
||||
}
|
||||
|
||||
// evaluate expression in parent scope
|
||||
context.visit(node.expression, {
|
||||
...context.state,
|
||||
expression: node.metadata.expression,
|
||||
scope: /** @type {Scope} */ (context.state.scope.parent)
|
||||
});
|
||||
|
||||
context.visit(node.body);
|
||||
if (node.key) context.visit(node.key);
|
||||
if (node.fallback) context.visit(node.fallback);
|
||||
|
||||
mark_subtree_dynamic(context.path);
|
||||
}
|
||||
20
node_modules/svelte/src/compiler/phases/2-analyze/visitors/ExportDefaultDeclaration.js
generated
vendored
Normal file
20
node_modules/svelte/src/compiler/phases/2-analyze/visitors/ExportDefaultDeclaration.js
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
/** @import { ExportDefaultDeclaration } from 'estree' */
|
||||
/** @import { Context } from '../types' */
|
||||
import * as e from '../../../errors.js';
|
||||
import { validate_export } from './shared/utils.js';
|
||||
|
||||
/**
|
||||
* @param {ExportDefaultDeclaration} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function ExportDefaultDeclaration(node, context) {
|
||||
if (!context.state.ast_type /* .svelte.js module */) {
|
||||
if (node.declaration.type === 'Identifier') {
|
||||
validate_export(node, context.state.scope, node.declaration.name);
|
||||
}
|
||||
} else {
|
||||
e.module_illegal_default_export(node);
|
||||
}
|
||||
|
||||
context.next();
|
||||
}
|
||||
61
node_modules/svelte/src/compiler/phases/2-analyze/visitors/ExportNamedDeclaration.js
generated
vendored
Normal file
61
node_modules/svelte/src/compiler/phases/2-analyze/visitors/ExportNamedDeclaration.js
generated
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
/** @import { ExportNamedDeclaration, Identifier, Node } from 'estree' */
|
||||
/** @import { Binding } from '#compiler' */
|
||||
/** @import { Context } from '../types' */
|
||||
/** @import { Scope } from '../../scope' */
|
||||
import * as e from '../../../errors.js';
|
||||
import { extract_identifiers } from '../../../utils/ast.js';
|
||||
|
||||
/**
|
||||
* @param {ExportNamedDeclaration} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function ExportNamedDeclaration(node, context) {
|
||||
// visit children, so bindings are correctly initialised
|
||||
context.next();
|
||||
|
||||
if (node.declaration?.type === 'VariableDeclaration') {
|
||||
// in runes mode, forbid `export let`
|
||||
if (
|
||||
context.state.analysis.runes &&
|
||||
context.state.ast_type === 'instance' &&
|
||||
node.declaration.kind === 'let'
|
||||
) {
|
||||
e.legacy_export_invalid(node);
|
||||
}
|
||||
|
||||
for (const declarator of node.declaration.declarations) {
|
||||
for (const id of extract_identifiers(declarator.id)) {
|
||||
const binding = context.state.scope.get(id.name);
|
||||
if (!binding) continue;
|
||||
|
||||
if (binding.kind === 'derived') {
|
||||
e.derived_invalid_export(node);
|
||||
}
|
||||
|
||||
if ((binding.kind === 'state' || binding.kind === 'raw_state') && binding.reassigned) {
|
||||
e.state_invalid_export(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (context.state.analysis.runes) {
|
||||
if (node.declaration && context.state.ast_type === 'instance') {
|
||||
if (
|
||||
node.declaration.type === 'FunctionDeclaration' ||
|
||||
node.declaration.type === 'ClassDeclaration'
|
||||
) {
|
||||
context.state.analysis.exports.push({
|
||||
name: /** @type {Identifier} */ (node.declaration.id).name,
|
||||
alias: null
|
||||
});
|
||||
} else if (node.declaration.kind === 'const') {
|
||||
for (const declarator of node.declaration.declarations) {
|
||||
for (const node of extract_identifiers(declarator.id)) {
|
||||
context.state.analysis.exports.push({ name: node.name, alias: null });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
30
node_modules/svelte/src/compiler/phases/2-analyze/visitors/ExportSpecifier.js
generated
vendored
Normal file
30
node_modules/svelte/src/compiler/phases/2-analyze/visitors/ExportSpecifier.js
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
/** @import { ExportSpecifier } from 'estree' */
|
||||
/** @import { Context } from '../types' */
|
||||
import { validate_export } from './shared/utils.js';
|
||||
|
||||
/**
|
||||
* @param {ExportSpecifier} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function ExportSpecifier(node, context) {
|
||||
const local_name =
|
||||
node.local.type === 'Identifier' ? node.local.name : /** @type {string} */ (node.local.value);
|
||||
const exported_name =
|
||||
node.exported.type === 'Identifier'
|
||||
? node.exported.name
|
||||
: /** @type {string} */ (node.exported.value);
|
||||
|
||||
if (context.state.ast_type === 'instance') {
|
||||
if (context.state.analysis.runes) {
|
||||
context.state.analysis.exports.push({
|
||||
name: local_name,
|
||||
alias: exported_name
|
||||
});
|
||||
|
||||
const binding = context.state.scope.get(local_name);
|
||||
if (binding) binding.reassigned = binding.updated = true;
|
||||
}
|
||||
} else {
|
||||
validate_export(node, context.state.scope, local_name);
|
||||
}
|
||||
}
|
||||
38
node_modules/svelte/src/compiler/phases/2-analyze/visitors/ExpressionStatement.js
generated
vendored
Normal file
38
node_modules/svelte/src/compiler/phases/2-analyze/visitors/ExpressionStatement.js
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
/** @import { ExpressionStatement, ImportDeclaration } from 'estree' */
|
||||
/** @import { Context } from '../types' */
|
||||
import * as w from '../../../warnings.js';
|
||||
|
||||
/**
|
||||
* @param {ExpressionStatement} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function ExpressionStatement(node, context) {
|
||||
// warn on `new Component({ target: ... })` if imported from a `.svelte` file
|
||||
if (
|
||||
node.expression.type === 'NewExpression' &&
|
||||
node.expression.callee.type === 'Identifier' &&
|
||||
node.expression.arguments.length === 1 &&
|
||||
node.expression.arguments[0].type === 'ObjectExpression' &&
|
||||
node.expression.arguments[0].properties.some(
|
||||
(p) => p.type === 'Property' && p.key.type === 'Identifier' && p.key.name === 'target'
|
||||
)
|
||||
) {
|
||||
const binding = context.state.scope.get(node.expression.callee.name);
|
||||
|
||||
if (binding?.kind === 'normal' && binding.declaration_kind === 'import') {
|
||||
const declaration = /** @type {ImportDeclaration} */ (binding.initial);
|
||||
|
||||
// Theoretically someone could import a class from a `.svelte.js` module, but that's too rare to worry about
|
||||
if (
|
||||
/** @type {string} */ (declaration.source.value).endsWith('.svelte') &&
|
||||
declaration.specifiers.find(
|
||||
(s) => s.local.name === binding.node.name && s.type === 'ImportDefaultSpecifier'
|
||||
)
|
||||
) {
|
||||
w.legacy_component_creation(node.expression);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context.next();
|
||||
}
|
||||
26
node_modules/svelte/src/compiler/phases/2-analyze/visitors/ExpressionTag.js
generated
vendored
Normal file
26
node_modules/svelte/src/compiler/phases/2-analyze/visitors/ExpressionTag.js
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
/** @import { AST } from '#compiler' */
|
||||
/** @import { Context } from '../types' */
|
||||
import { is_tag_valid_with_parent } from '../../../../html-tree-validation.js';
|
||||
import * as e from '../../../errors.js';
|
||||
import { mark_subtree_dynamic } from './shared/fragment.js';
|
||||
|
||||
/**
|
||||
* @param {AST.ExpressionTag} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function ExpressionTag(node, context) {
|
||||
const in_template = context.path.at(-1)?.type === 'Fragment';
|
||||
|
||||
if (in_template && context.state.parent_element) {
|
||||
const message = is_tag_valid_with_parent('#text', context.state.parent_element);
|
||||
if (message) {
|
||||
e.node_invalid_placement(node, message);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO ideally we wouldn't do this here, we'd just do it on encountering
|
||||
// an `Identifier` within the tag. But we currently need to handle `{42}` etc
|
||||
mark_subtree_dynamic(context.path);
|
||||
|
||||
context.next({ ...context.state, expression: node.metadata.expression });
|
||||
}
|
||||
16
node_modules/svelte/src/compiler/phases/2-analyze/visitors/FunctionDeclaration.js
generated
vendored
Normal file
16
node_modules/svelte/src/compiler/phases/2-analyze/visitors/FunctionDeclaration.js
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
/** @import { FunctionDeclaration } from 'estree' */
|
||||
/** @import { Context } from '../types' */
|
||||
import { visit_function } from './shared/function.js';
|
||||
import { validate_identifier_name } from './shared/utils.js';
|
||||
|
||||
/**
|
||||
* @param {FunctionDeclaration} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function FunctionDeclaration(node, context) {
|
||||
if (context.state.analysis.runes && node.id !== null) {
|
||||
validate_identifier_name(context.state.scope.get(node.id.name));
|
||||
}
|
||||
|
||||
visit_function(node, context);
|
||||
}
|
||||
11
node_modules/svelte/src/compiler/phases/2-analyze/visitors/FunctionExpression.js
generated
vendored
Normal file
11
node_modules/svelte/src/compiler/phases/2-analyze/visitors/FunctionExpression.js
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
/** @import { FunctionExpression } from 'estree' */
|
||||
/** @import { Context } from '../types' */
|
||||
import { visit_function } from './shared/function.js';
|
||||
|
||||
/**
|
||||
* @param {FunctionExpression} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function FunctionExpression(node, context) {
|
||||
visit_function(node, context);
|
||||
}
|
||||
19
node_modules/svelte/src/compiler/phases/2-analyze/visitors/HtmlTag.js
generated
vendored
Normal file
19
node_modules/svelte/src/compiler/phases/2-analyze/visitors/HtmlTag.js
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
/** @import { AST } from '#compiler' */
|
||||
/** @import { Context } from '../types' */
|
||||
import { mark_subtree_dynamic } from './shared/fragment.js';
|
||||
import { validate_opening_tag } from './shared/utils.js';
|
||||
|
||||
/**
|
||||
* @param {AST.HtmlTag} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function HtmlTag(node, context) {
|
||||
if (context.state.analysis.runes) {
|
||||
validate_opening_tag(node, context.state, '@');
|
||||
}
|
||||
|
||||
// unfortunately this is necessary in order to fix invalid HTML
|
||||
mark_subtree_dynamic(context.path);
|
||||
|
||||
context.next();
|
||||
}
|
||||
125
node_modules/svelte/src/compiler/phases/2-analyze/visitors/Identifier.js
generated
vendored
Normal file
125
node_modules/svelte/src/compiler/phases/2-analyze/visitors/Identifier.js
generated
vendored
Normal file
@@ -0,0 +1,125 @@
|
||||
/** @import { Expression, Identifier } from 'estree' */
|
||||
/** @import { EachBlock } from '#compiler' */
|
||||
/** @import { Context } from '../types' */
|
||||
import is_reference from 'is-reference';
|
||||
import { should_proxy } from '../../3-transform/client/utils.js';
|
||||
import * as e from '../../../errors.js';
|
||||
import * as w from '../../../warnings.js';
|
||||
import { is_rune } from '../../../../utils.js';
|
||||
import { mark_subtree_dynamic } from './shared/fragment.js';
|
||||
|
||||
/**
|
||||
* @param {Identifier} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function Identifier(node, context) {
|
||||
let i = context.path.length;
|
||||
let parent = /** @type {Expression} */ (context.path[--i]);
|
||||
|
||||
if (!is_reference(node, parent)) {
|
||||
return;
|
||||
}
|
||||
|
||||
mark_subtree_dynamic(context.path);
|
||||
|
||||
// If we are using arguments outside of a function, then throw an error
|
||||
if (
|
||||
node.name === 'arguments' &&
|
||||
!context.path.some((n) => n.type === 'FunctionDeclaration' || n.type === 'FunctionExpression')
|
||||
) {
|
||||
e.invalid_arguments_usage(node);
|
||||
}
|
||||
|
||||
// `$$slots` exists even in runes mode
|
||||
if (node.name === '$$slots') {
|
||||
context.state.analysis.uses_slots = true;
|
||||
}
|
||||
|
||||
if (context.state.analysis.runes) {
|
||||
if (
|
||||
is_rune(node.name) &&
|
||||
context.state.scope.get(node.name) === null &&
|
||||
context.state.scope.get(node.name.slice(1)) === null
|
||||
) {
|
||||
/** @type {Expression} */
|
||||
let current = node;
|
||||
let name = node.name;
|
||||
|
||||
while (parent.type === 'MemberExpression') {
|
||||
if (parent.computed) e.rune_invalid_computed_property(parent);
|
||||
name += `.${/** @type {Identifier} */ (parent.property).name}`;
|
||||
|
||||
current = parent;
|
||||
parent = /** @type {Expression} */ (context.path[--i]);
|
||||
|
||||
if (!is_rune(name)) {
|
||||
if (name === '$effect.active') {
|
||||
e.rune_renamed(parent, '$effect.active', '$effect.tracking');
|
||||
}
|
||||
|
||||
if (name === '$state.frozen') {
|
||||
e.rune_renamed(parent, '$state.frozen', '$state.raw');
|
||||
}
|
||||
|
||||
if (name === '$state.is') {
|
||||
e.rune_removed(parent, '$state.is');
|
||||
}
|
||||
|
||||
e.rune_invalid_name(parent, name);
|
||||
}
|
||||
}
|
||||
|
||||
if (parent.type !== 'CallExpression') {
|
||||
e.rune_missing_parentheses(current);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let binding = context.state.scope.get(node.name);
|
||||
|
||||
if (!context.state.analysis.runes) {
|
||||
if (node.name === '$$props') {
|
||||
context.state.analysis.uses_props = true;
|
||||
}
|
||||
|
||||
if (node.name === '$$restProps') {
|
||||
context.state.analysis.uses_rest_props = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (binding) {
|
||||
if (context.state.expression) {
|
||||
context.state.expression.dependencies.add(binding);
|
||||
context.state.expression.has_state ||= binding.kind !== 'normal';
|
||||
}
|
||||
|
||||
if (
|
||||
context.state.analysis.runes &&
|
||||
node !== binding.node &&
|
||||
context.state.function_depth === binding.scope.function_depth &&
|
||||
// If we have $state that can be proxied or frozen and isn't re-assigned, then that means
|
||||
// it's likely not using a primitive value and thus this warning isn't that helpful.
|
||||
((binding.kind === 'state' &&
|
||||
(binding.reassigned ||
|
||||
(binding.initial?.type === 'CallExpression' &&
|
||||
binding.initial.arguments.length === 1 &&
|
||||
binding.initial.arguments[0].type !== 'SpreadElement' &&
|
||||
!should_proxy(binding.initial.arguments[0], context.state.scope)))) ||
|
||||
binding.kind === 'raw_state' ||
|
||||
binding.kind === 'derived') &&
|
||||
// We're only concerned with reads here
|
||||
(parent.type !== 'AssignmentExpression' || parent.left !== node) &&
|
||||
parent.type !== 'UpdateExpression'
|
||||
) {
|
||||
w.state_referenced_locally(node);
|
||||
}
|
||||
|
||||
if (
|
||||
context.state.reactive_statement &&
|
||||
binding.scope === context.state.analysis.module.scope &&
|
||||
binding.reassigned
|
||||
) {
|
||||
w.reactive_declaration_module_script_dependency(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
21
node_modules/svelte/src/compiler/phases/2-analyze/visitors/IfBlock.js
generated
vendored
Normal file
21
node_modules/svelte/src/compiler/phases/2-analyze/visitors/IfBlock.js
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
/** @import { AST } from '#compiler' */
|
||||
/** @import { Context } from '../types' */
|
||||
import { mark_subtree_dynamic } from './shared/fragment.js';
|
||||
import { validate_block_not_empty, validate_opening_tag } from './shared/utils.js';
|
||||
|
||||
/**
|
||||
* @param {AST.IfBlock} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function IfBlock(node, context) {
|
||||
validate_block_not_empty(node.consequent, context);
|
||||
validate_block_not_empty(node.alternate, context);
|
||||
|
||||
if (context.state.analysis.runes) {
|
||||
validate_opening_tag(node, context.state, node.elseif ? ':' : '#');
|
||||
}
|
||||
|
||||
mark_subtree_dynamic(context.path);
|
||||
|
||||
context.next();
|
||||
}
|
||||
31
node_modules/svelte/src/compiler/phases/2-analyze/visitors/ImportDeclaration.js
generated
vendored
Normal file
31
node_modules/svelte/src/compiler/phases/2-analyze/visitors/ImportDeclaration.js
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
/** @import { ImportDeclaration } from 'estree' */
|
||||
/** @import { Context } from '../types' */
|
||||
import * as e from '../../../errors.js';
|
||||
|
||||
/**
|
||||
* @param {ImportDeclaration} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function ImportDeclaration(node, context) {
|
||||
if (context.state.analysis.runes) {
|
||||
const source = /** @type {string} */ (node.source.value);
|
||||
|
||||
if (source.startsWith('svelte/internal')) {
|
||||
e.import_svelte_internal_forbidden(node);
|
||||
}
|
||||
|
||||
if (source === 'svelte') {
|
||||
for (const specifier of node.specifiers) {
|
||||
if (specifier.type === 'ImportSpecifier') {
|
||||
if (
|
||||
specifier.imported.type === 'Identifier' &&
|
||||
(specifier.imported.name === 'beforeUpdate' ||
|
||||
specifier.imported.name === 'afterUpdate')
|
||||
) {
|
||||
e.runes_mode_invalid_import(specifier, specifier.imported.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
20
node_modules/svelte/src/compiler/phases/2-analyze/visitors/KeyBlock.js
generated
vendored
Normal file
20
node_modules/svelte/src/compiler/phases/2-analyze/visitors/KeyBlock.js
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
/** @import { AST } from '#compiler' */
|
||||
/** @import { Context } from '../types' */
|
||||
import { mark_subtree_dynamic } from './shared/fragment.js';
|
||||
import { validate_block_not_empty, validate_opening_tag } from './shared/utils.js';
|
||||
|
||||
/**
|
||||
* @param {AST.KeyBlock} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function KeyBlock(node, context) {
|
||||
validate_block_not_empty(node.fragment, context);
|
||||
|
||||
if (context.state.analysis.runes) {
|
||||
validate_opening_tag(node, context.state, '#');
|
||||
}
|
||||
|
||||
mark_subtree_dynamic(context.path);
|
||||
|
||||
context.next();
|
||||
}
|
||||
95
node_modules/svelte/src/compiler/phases/2-analyze/visitors/LabeledStatement.js
generated
vendored
Normal file
95
node_modules/svelte/src/compiler/phases/2-analyze/visitors/LabeledStatement.js
generated
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
/** @import { Expression, LabeledStatement } from 'estree' */
|
||||
/** @import { AST, ReactiveStatement } from '#compiler' */
|
||||
/** @import { Context } from '../types' */
|
||||
import * as e from '../../../errors.js';
|
||||
import { extract_identifiers, object } from '../../../utils/ast.js';
|
||||
import * as w from '../../../warnings.js';
|
||||
|
||||
/**
|
||||
* @param {LabeledStatement} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function LabeledStatement(node, context) {
|
||||
if (node.label.name === '$') {
|
||||
const parent = /** @type {AST.SvelteNode} */ (context.path.at(-1));
|
||||
|
||||
const is_reactive_statement =
|
||||
context.state.ast_type === 'instance' && parent.type === 'Program';
|
||||
|
||||
if (is_reactive_statement) {
|
||||
if (context.state.analysis.runes) {
|
||||
e.legacy_reactive_statement_invalid(node);
|
||||
}
|
||||
|
||||
// Find all dependencies of this `$: {...}` statement
|
||||
/** @type {ReactiveStatement} */
|
||||
const reactive_statement = {
|
||||
assignments: new Set(),
|
||||
dependencies: []
|
||||
};
|
||||
|
||||
context.next({
|
||||
...context.state,
|
||||
reactive_statement,
|
||||
function_depth: context.state.scope.function_depth + 1
|
||||
});
|
||||
|
||||
// Every referenced binding becomes a dependency, unless it's on
|
||||
// the left-hand side of an `=` assignment
|
||||
for (const [name, nodes] of context.state.scope.references) {
|
||||
const binding = context.state.scope.get(name);
|
||||
if (binding === null) continue;
|
||||
|
||||
for (const { node, path } of nodes) {
|
||||
/** @type {Expression} */
|
||||
let left = node;
|
||||
|
||||
let i = path.length - 1;
|
||||
let parent = /** @type {Expression} */ (path.at(i));
|
||||
while (parent.type === 'MemberExpression') {
|
||||
left = parent;
|
||||
parent = /** @type {Expression} */ (path.at(--i));
|
||||
}
|
||||
|
||||
if (
|
||||
parent.type === 'AssignmentExpression' &&
|
||||
parent.operator === '=' &&
|
||||
parent.left === left
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
reactive_statement.dependencies.push(binding);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
context.state.reactive_statements.set(node, reactive_statement);
|
||||
|
||||
if (
|
||||
node.body.type === 'ExpressionStatement' &&
|
||||
node.body.expression.type === 'AssignmentExpression'
|
||||
) {
|
||||
let ids = extract_identifiers(node.body.expression.left);
|
||||
if (node.body.expression.left.type === 'MemberExpression') {
|
||||
const id = object(node.body.expression.left);
|
||||
if (id !== null) {
|
||||
ids = [id];
|
||||
}
|
||||
}
|
||||
|
||||
for (const id of ids) {
|
||||
const binding = context.state.scope.get(id.name);
|
||||
if (binding?.kind === 'legacy_reactive') {
|
||||
// TODO does this include `let double; $: double = x * 2`?
|
||||
binding.legacy_dependencies = Array.from(reactive_statement.dependencies);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (!context.state.analysis.runes) {
|
||||
w.reactive_declaration_invalid_placement(node);
|
||||
}
|
||||
}
|
||||
|
||||
context.next();
|
||||
}
|
||||
24
node_modules/svelte/src/compiler/phases/2-analyze/visitors/LetDirective.js
generated
vendored
Normal file
24
node_modules/svelte/src/compiler/phases/2-analyze/visitors/LetDirective.js
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
/** @import { AST } from '#compiler' */
|
||||
/** @import { Context } from '../types' */
|
||||
import * as e from '../../../errors.js';
|
||||
|
||||
/**
|
||||
* @param {AST.LetDirective} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function LetDirective(node, context) {
|
||||
const parent = context.path.at(-1);
|
||||
|
||||
if (
|
||||
parent === undefined ||
|
||||
(parent.type !== 'Component' &&
|
||||
parent.type !== 'RegularElement' &&
|
||||
parent.type !== 'SlotElement' &&
|
||||
parent.type !== 'SvelteElement' &&
|
||||
parent.type !== 'SvelteComponent' &&
|
||||
parent.type !== 'SvelteSelf' &&
|
||||
parent.type !== 'SvelteFragment')
|
||||
) {
|
||||
e.let_directive_invalid_placement(node);
|
||||
}
|
||||
}
|
||||
30
node_modules/svelte/src/compiler/phases/2-analyze/visitors/MemberExpression.js
generated
vendored
Normal file
30
node_modules/svelte/src/compiler/phases/2-analyze/visitors/MemberExpression.js
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
/** @import { MemberExpression, Node } from 'estree' */
|
||||
/** @import { Context } from '../types' */
|
||||
import * as e from '../../../errors.js';
|
||||
import * as w from '../../../warnings.js';
|
||||
import { object } from '../../../utils/ast.js';
|
||||
import { is_pure, is_safe_identifier } from './shared/utils.js';
|
||||
import { mark_subtree_dynamic } from './shared/fragment.js';
|
||||
|
||||
/**
|
||||
* @param {MemberExpression} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function MemberExpression(node, context) {
|
||||
if (node.object.type === 'Identifier' && node.property.type === 'Identifier') {
|
||||
const binding = context.state.scope.get(node.object.name);
|
||||
if (binding?.kind === 'rest_prop' && node.property.name.startsWith('$$')) {
|
||||
e.props_illegal_name(node.property);
|
||||
}
|
||||
}
|
||||
|
||||
if (context.state.expression && !is_pure(node, context)) {
|
||||
context.state.expression.has_state = true;
|
||||
}
|
||||
|
||||
if (!is_safe_identifier(node, context.state.scope)) {
|
||||
context.state.analysis.needs_context = true;
|
||||
}
|
||||
|
||||
context.next();
|
||||
}
|
||||
17
node_modules/svelte/src/compiler/phases/2-analyze/visitors/NewExpression.js
generated
vendored
Normal file
17
node_modules/svelte/src/compiler/phases/2-analyze/visitors/NewExpression.js
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
/** @import { NewExpression } from 'estree' */
|
||||
/** @import { Context } from '../types' */
|
||||
import * as w from '../../../warnings.js';
|
||||
|
||||
/**
|
||||
* @param {NewExpression} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function NewExpression(node, context) {
|
||||
if (node.callee.type === 'ClassExpression' && context.state.scope.function_depth > 0) {
|
||||
w.perf_avoid_inline_class(node);
|
||||
}
|
||||
|
||||
context.state.analysis.needs_context = true;
|
||||
|
||||
context.next();
|
||||
}
|
||||
28
node_modules/svelte/src/compiler/phases/2-analyze/visitors/OnDirective.js
generated
vendored
Normal file
28
node_modules/svelte/src/compiler/phases/2-analyze/visitors/OnDirective.js
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
/** @import { AST } from '#compiler' */
|
||||
/** @import { Context } from '../types' */
|
||||
import * as w from '../../../warnings.js';
|
||||
import { mark_subtree_dynamic } from './shared/fragment.js';
|
||||
|
||||
/**
|
||||
* @param {AST.OnDirective} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function OnDirective(node, context) {
|
||||
if (context.state.analysis.runes) {
|
||||
const parent_type = context.path.at(-1)?.type;
|
||||
|
||||
// Don't warn on component events; these might not be under the author's control so the warning would be unactionable
|
||||
if (parent_type === 'RegularElement' || parent_type === 'SvelteElement') {
|
||||
w.event_directive_deprecated(node, node.name);
|
||||
}
|
||||
}
|
||||
|
||||
const parent = context.path.at(-1);
|
||||
if (parent?.type === 'SvelteElement' || parent?.type === 'RegularElement') {
|
||||
context.state.analysis.event_directive_node ??= node;
|
||||
}
|
||||
|
||||
mark_subtree_dynamic(context.path);
|
||||
|
||||
context.next({ ...context.state, expression: node.metadata.expression });
|
||||
}
|
||||
195
node_modules/svelte/src/compiler/phases/2-analyze/visitors/RegularElement.js
generated
vendored
Normal file
195
node_modules/svelte/src/compiler/phases/2-analyze/visitors/RegularElement.js
generated
vendored
Normal file
@@ -0,0 +1,195 @@
|
||||
/** @import { AST } from '#compiler' */
|
||||
/** @import { Context } from '../types' */
|
||||
import { is_mathml, is_svg, is_void } from '../../../../utils.js';
|
||||
import {
|
||||
is_tag_valid_with_ancestor,
|
||||
is_tag_valid_with_parent
|
||||
} from '../../../../html-tree-validation.js';
|
||||
import * as e from '../../../errors.js';
|
||||
import * as w from '../../../warnings.js';
|
||||
import { create_attribute, is_custom_element_node } from '../../nodes.js';
|
||||
import { regex_starts_with_newline } from '../../patterns.js';
|
||||
import { check_element } from './shared/a11y.js';
|
||||
import { validate_element } from './shared/element.js';
|
||||
import { mark_subtree_dynamic } from './shared/fragment.js';
|
||||
|
||||
/**
|
||||
* @param {AST.RegularElement} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function RegularElement(node, context) {
|
||||
validate_element(node, context);
|
||||
check_element(node, context);
|
||||
|
||||
node.metadata.path = [...context.path];
|
||||
context.state.analysis.elements.push(node);
|
||||
|
||||
// Special case: Move the children of <textarea> into a value attribute if they are dynamic
|
||||
if (node.name === 'textarea' && node.fragment.nodes.length > 0) {
|
||||
for (const attribute of node.attributes) {
|
||||
if (attribute.type === 'Attribute' && attribute.name === 'value') {
|
||||
e.textarea_invalid_content(node);
|
||||
}
|
||||
}
|
||||
|
||||
if (node.fragment.nodes.length > 1 || node.fragment.nodes[0].type !== 'Text') {
|
||||
const first = node.fragment.nodes[0];
|
||||
if (first.type === 'Text') {
|
||||
// The leading newline character needs to be stripped because of a qirk:
|
||||
// It is ignored by browsers if the tag and its contents are set through
|
||||
// innerHTML, but we're now setting it through the value property at which
|
||||
// point it is _not_ ignored, so we need to strip it ourselves.
|
||||
// see https://html.spec.whatwg.org/multipage/syntax.html#element-restrictions
|
||||
// see https://html.spec.whatwg.org/multipage/grouping-content.html#the-pre-element
|
||||
first.data = first.data.replace(regex_starts_with_newline, '');
|
||||
first.raw = first.raw.replace(regex_starts_with_newline, '');
|
||||
}
|
||||
|
||||
node.attributes.push(
|
||||
create_attribute(
|
||||
'value',
|
||||
/** @type {AST.Text} */ (node.fragment.nodes.at(0)).start,
|
||||
/** @type {AST.Text} */ (node.fragment.nodes.at(-1)).end,
|
||||
// @ts-ignore
|
||||
node.fragment.nodes
|
||||
)
|
||||
);
|
||||
|
||||
node.fragment.nodes = [];
|
||||
}
|
||||
}
|
||||
|
||||
// Special case: single expression tag child of option element -> add "fake" attribute
|
||||
// to ensure that value types are the same (else for example numbers would be strings)
|
||||
if (
|
||||
node.name === 'option' &&
|
||||
node.fragment.nodes?.length === 1 &&
|
||||
node.fragment.nodes[0].type === 'ExpressionTag' &&
|
||||
!node.attributes.some(
|
||||
(attribute) => attribute.type === 'Attribute' && attribute.name === 'value'
|
||||
)
|
||||
) {
|
||||
const child = node.fragment.nodes[0];
|
||||
node.attributes.push(create_attribute('value', child.start, child.end, [child]));
|
||||
}
|
||||
|
||||
const binding = context.state.scope.get(node.name);
|
||||
if (
|
||||
binding !== null &&
|
||||
binding.declaration_kind === 'import' &&
|
||||
binding.references.length === 0
|
||||
) {
|
||||
w.component_name_lowercase(node, node.name);
|
||||
}
|
||||
|
||||
node.metadata.has_spread = node.attributes.some(
|
||||
(attribute) => attribute.type === 'SpreadAttribute'
|
||||
);
|
||||
|
||||
const is_svg_element = () => {
|
||||
if (is_svg(node.name)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (node.name === 'a' || node.name === 'title') {
|
||||
let i = context.path.length;
|
||||
|
||||
while (i--) {
|
||||
const ancestor = context.path[i];
|
||||
if (ancestor.type === 'RegularElement') {
|
||||
return ancestor.metadata.svg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
node.metadata.svg = is_svg_element();
|
||||
node.metadata.mathml = is_mathml(node.name);
|
||||
|
||||
if (is_custom_element_node(node) && node.attributes.length > 0) {
|
||||
// we're setting all attributes on custom elements through properties
|
||||
mark_subtree_dynamic(context.path);
|
||||
}
|
||||
|
||||
if (context.state.parent_element) {
|
||||
let past_parent = false;
|
||||
let only_warn = false;
|
||||
const ancestors = [context.state.parent_element];
|
||||
|
||||
for (let i = context.path.length - 1; i >= 0; i--) {
|
||||
const ancestor = context.path[i];
|
||||
|
||||
if (
|
||||
ancestor.type === 'IfBlock' ||
|
||||
ancestor.type === 'EachBlock' ||
|
||||
ancestor.type === 'AwaitBlock' ||
|
||||
ancestor.type === 'KeyBlock'
|
||||
) {
|
||||
// We're creating a separate template string inside blocks, which means client-side this would work
|
||||
only_warn = true;
|
||||
}
|
||||
|
||||
if (!past_parent) {
|
||||
if (ancestor.type === 'RegularElement' && ancestor.name === context.state.parent_element) {
|
||||
const message = is_tag_valid_with_parent(node.name, context.state.parent_element);
|
||||
if (message) {
|
||||
if (only_warn) {
|
||||
w.node_invalid_placement_ssr(node, message);
|
||||
} else {
|
||||
e.node_invalid_placement(node, message);
|
||||
}
|
||||
}
|
||||
|
||||
past_parent = true;
|
||||
}
|
||||
} else if (ancestor.type === 'RegularElement') {
|
||||
ancestors.push(ancestor.name);
|
||||
|
||||
const message = is_tag_valid_with_ancestor(node.name, ancestors);
|
||||
if (message) {
|
||||
if (only_warn) {
|
||||
w.node_invalid_placement_ssr(node, message);
|
||||
} else {
|
||||
e.node_invalid_placement(node, message);
|
||||
}
|
||||
}
|
||||
} else if (
|
||||
ancestor.type === 'Component' ||
|
||||
ancestor.type === 'SvelteComponent' ||
|
||||
ancestor.type === 'SvelteElement' ||
|
||||
ancestor.type === 'SvelteSelf' ||
|
||||
ancestor.type === 'SnippetBlock'
|
||||
) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Strip off any namespace from the beginning of the node name.
|
||||
const node_name = node.name.replace(/[a-zA-Z-]*:/g, '');
|
||||
|
||||
if (
|
||||
context.state.analysis.source[node.end - 2] === '/' &&
|
||||
!is_void(node_name) &&
|
||||
!is_svg(node_name)
|
||||
) {
|
||||
w.element_invalid_self_closing_tag(node, node.name);
|
||||
}
|
||||
|
||||
context.next({ ...context.state, parent_element: node.name });
|
||||
|
||||
// Special case: <a> tags are valid in both the SVG and HTML namespace.
|
||||
// If there's no parent, look downwards to see if it's the parent of a SVG or HTML element.
|
||||
if (node.name === 'a' && !context.state.parent_element) {
|
||||
for (const child of node.fragment.nodes) {
|
||||
if (child.type === 'RegularElement') {
|
||||
if (child.metadata.svg && child.name !== 'svg') {
|
||||
node.metadata.svg = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
68
node_modules/svelte/src/compiler/phases/2-analyze/visitors/RenderTag.js
generated
vendored
Normal file
68
node_modules/svelte/src/compiler/phases/2-analyze/visitors/RenderTag.js
generated
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
/** @import { AST } from '#compiler' */
|
||||
/** @import { Context } from '../types' */
|
||||
import { unwrap_optional } from '../../../utils/ast.js';
|
||||
import * as e from '../../../errors.js';
|
||||
import { validate_opening_tag } from './shared/utils.js';
|
||||
import { mark_subtree_dynamic } from './shared/fragment.js';
|
||||
import { is_resolved_snippet } from './shared/snippets.js';
|
||||
import { create_expression_metadata } from '../../nodes.js';
|
||||
|
||||
/**
|
||||
* @param {AST.RenderTag} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function RenderTag(node, context) {
|
||||
validate_opening_tag(node, context.state, '@');
|
||||
|
||||
node.metadata.path = [...context.path];
|
||||
|
||||
const expression = unwrap_optional(node.expression);
|
||||
const callee = expression.callee;
|
||||
|
||||
const binding = callee.type === 'Identifier' ? context.state.scope.get(callee.name) : null;
|
||||
|
||||
node.metadata.dynamic = binding?.kind !== 'normal';
|
||||
|
||||
/**
|
||||
* If we can't unambiguously resolve this to a declaration, we
|
||||
* must assume the worst and link the render tag to every snippet
|
||||
*/
|
||||
let resolved = callee.type === 'Identifier' && is_resolved_snippet(binding);
|
||||
|
||||
if (binding?.initial?.type === 'SnippetBlock') {
|
||||
// if this render tag unambiguously references a local snippet, our job is easy
|
||||
node.metadata.snippets.add(binding.initial);
|
||||
}
|
||||
|
||||
context.state.analysis.snippet_renderers.set(node, resolved);
|
||||
context.state.analysis.uses_render_tags = true;
|
||||
|
||||
const raw_args = unwrap_optional(node.expression).arguments;
|
||||
for (const arg of raw_args) {
|
||||
if (arg.type === 'SpreadElement') {
|
||||
e.render_tag_invalid_spread_argument(arg);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
callee.type === 'MemberExpression' &&
|
||||
callee.property.type === 'Identifier' &&
|
||||
['bind', 'apply', 'call'].includes(callee.property.name)
|
||||
) {
|
||||
e.render_tag_invalid_call_expression(node);
|
||||
}
|
||||
|
||||
mark_subtree_dynamic(context.path);
|
||||
|
||||
context.visit(callee);
|
||||
|
||||
for (const arg of expression.arguments) {
|
||||
const metadata = create_expression_metadata();
|
||||
node.metadata.arguments.push(metadata);
|
||||
|
||||
context.visit(arg, {
|
||||
...context.state,
|
||||
expression: metadata
|
||||
});
|
||||
}
|
||||
}
|
||||
42
node_modules/svelte/src/compiler/phases/2-analyze/visitors/SlotElement.js
generated
vendored
Normal file
42
node_modules/svelte/src/compiler/phases/2-analyze/visitors/SlotElement.js
generated
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
/** @import { AST } from '#compiler' */
|
||||
/** @import { Context } from '../types' */
|
||||
import { is_text_attribute } from '../../../utils/ast.js';
|
||||
import * as e from '../../../errors.js';
|
||||
import * as w from '../../../warnings.js';
|
||||
import { mark_subtree_dynamic } from './shared/fragment.js';
|
||||
|
||||
/**
|
||||
* @param {AST.SlotElement} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function SlotElement(node, context) {
|
||||
if (context.state.analysis.runes && !context.state.analysis.custom_element) {
|
||||
w.slot_element_deprecated(node);
|
||||
}
|
||||
|
||||
mark_subtree_dynamic(context.path);
|
||||
|
||||
/** @type {string} */
|
||||
let name = 'default';
|
||||
|
||||
for (const attribute of node.attributes) {
|
||||
if (attribute.type === 'Attribute') {
|
||||
if (attribute.name === 'name') {
|
||||
if (!is_text_attribute(attribute)) {
|
||||
e.slot_element_invalid_name(attribute);
|
||||
}
|
||||
|
||||
name = attribute.value[0].data;
|
||||
if (name === 'default') {
|
||||
e.slot_element_invalid_name_default(attribute);
|
||||
}
|
||||
}
|
||||
} else if (attribute.type !== 'SpreadAttribute' && attribute.type !== 'LetDirective') {
|
||||
e.slot_element_invalid_attribute(attribute);
|
||||
}
|
||||
}
|
||||
|
||||
context.state.analysis.slot_names.set(name, node);
|
||||
|
||||
context.next();
|
||||
}
|
||||
113
node_modules/svelte/src/compiler/phases/2-analyze/visitors/SnippetBlock.js
generated
vendored
Normal file
113
node_modules/svelte/src/compiler/phases/2-analyze/visitors/SnippetBlock.js
generated
vendored
Normal file
@@ -0,0 +1,113 @@
|
||||
/** @import { AST, Binding } from '#compiler' */
|
||||
/** @import { Scope } from '../../scope' */
|
||||
/** @import { Context } from '../types' */
|
||||
import { validate_block_not_empty, validate_opening_tag } from './shared/utils.js';
|
||||
import * as e from '../../../errors.js';
|
||||
|
||||
/**
|
||||
* @param {AST.SnippetBlock} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function SnippetBlock(node, context) {
|
||||
context.state.analysis.snippets.add(node);
|
||||
|
||||
validate_block_not_empty(node.body, context);
|
||||
|
||||
if (context.state.analysis.runes) {
|
||||
validate_opening_tag(node, context.state, '#');
|
||||
}
|
||||
|
||||
for (const arg of node.parameters) {
|
||||
if (arg.type === 'RestElement') {
|
||||
e.snippet_invalid_rest_parameter(arg);
|
||||
}
|
||||
}
|
||||
|
||||
context.next({ ...context.state, parent_element: null });
|
||||
|
||||
const can_hoist =
|
||||
context.path.length === 1 &&
|
||||
context.path[0].type === 'Fragment' &&
|
||||
can_hoist_snippet(context.state.scope, context.state.scopes);
|
||||
|
||||
const name = node.expression.name;
|
||||
|
||||
if (can_hoist) {
|
||||
const binding = /** @type {Binding} */ (context.state.scope.get(name));
|
||||
context.state.analysis.module.scope.declarations.set(name, binding);
|
||||
} else {
|
||||
const undefined_export = context.state.analysis.undefined_exports.get(name);
|
||||
if (undefined_export) {
|
||||
e.snippet_invalid_export(undefined_export);
|
||||
}
|
||||
}
|
||||
|
||||
node.metadata.can_hoist = can_hoist;
|
||||
|
||||
const { path } = context;
|
||||
const parent = path.at(-2);
|
||||
if (!parent) return;
|
||||
|
||||
if (
|
||||
parent.type === 'Component' &&
|
||||
parent.attributes.some(
|
||||
(attribute) =>
|
||||
(attribute.type === 'Attribute' || attribute.type === 'BindDirective') &&
|
||||
attribute.name === node.expression.name
|
||||
)
|
||||
) {
|
||||
e.snippet_shadowing_prop(node, node.expression.name);
|
||||
}
|
||||
|
||||
if (node.expression.name !== 'children') return;
|
||||
|
||||
if (
|
||||
parent.type === 'Component' ||
|
||||
parent.type === 'SvelteComponent' ||
|
||||
parent.type === 'SvelteSelf'
|
||||
) {
|
||||
if (
|
||||
parent.fragment.nodes.some(
|
||||
(node) =>
|
||||
node.type !== 'SnippetBlock' &&
|
||||
(node.type !== 'Text' || node.data.trim()) &&
|
||||
node.type !== 'Comment'
|
||||
)
|
||||
) {
|
||||
e.snippet_conflict(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Map<AST.SvelteNode, Scope>} scopes
|
||||
* @param {Scope} scope
|
||||
*/
|
||||
function can_hoist_snippet(scope, scopes, visited = new Set()) {
|
||||
for (const [reference] of scope.references) {
|
||||
const binding = scope.get(reference);
|
||||
|
||||
if (!binding || binding.scope.function_depth === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// ignore bindings declared inside the snippet (e.g. the snippet's own parameters)
|
||||
if (binding.scope.function_depth >= scope.function_depth) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (binding.initial?.type === 'SnippetBlock') {
|
||||
if (visited.has(binding)) continue;
|
||||
visited.add(binding);
|
||||
const snippet_scope = /** @type {Scope} */ (scopes.get(binding.initial));
|
||||
|
||||
if (can_hoist_snippet(snippet_scope, scopes, visited)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
13
node_modules/svelte/src/compiler/phases/2-analyze/visitors/SpreadAttribute.js
generated
vendored
Normal file
13
node_modules/svelte/src/compiler/phases/2-analyze/visitors/SpreadAttribute.js
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
/** @import { AST } from '#compiler' */
|
||||
/** @import { Context } from '../types' */
|
||||
|
||||
import { mark_subtree_dynamic } from './shared/fragment.js';
|
||||
|
||||
/**
|
||||
* @param {AST.SpreadAttribute} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function SpreadAttribute(node, context) {
|
||||
mark_subtree_dynamic(context.path);
|
||||
context.next({ ...context.state, expression: node.metadata.expression });
|
||||
}
|
||||
16
node_modules/svelte/src/compiler/phases/2-analyze/visitors/SpreadElement.js
generated
vendored
Normal file
16
node_modules/svelte/src/compiler/phases/2-analyze/visitors/SpreadElement.js
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
/** @import { SpreadElement } from 'estree' */
|
||||
/** @import { Context } from '../types' */
|
||||
|
||||
/**
|
||||
* @param {SpreadElement} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function SpreadElement(node, context) {
|
||||
if (context.state.expression) {
|
||||
// treat e.g. `[...x]` the same as `[...x.values()]`
|
||||
context.state.expression.has_call = true;
|
||||
context.state.expression.has_state = true;
|
||||
}
|
||||
|
||||
context.next();
|
||||
}
|
||||
37
node_modules/svelte/src/compiler/phases/2-analyze/visitors/StyleDirective.js
generated
vendored
Normal file
37
node_modules/svelte/src/compiler/phases/2-analyze/visitors/StyleDirective.js
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
/** @import { AST } from '#compiler' */
|
||||
/** @import { Context } from '../types' */
|
||||
import * as e from '../../../errors.js';
|
||||
import { get_attribute_chunks } from '../../../utils/ast.js';
|
||||
import { mark_subtree_dynamic } from './shared/fragment.js';
|
||||
|
||||
/**
|
||||
* @param {AST.StyleDirective} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function StyleDirective(node, context) {
|
||||
if (node.modifiers.length > 1 || (node.modifiers.length && node.modifiers[0] !== 'important')) {
|
||||
e.style_directive_invalid_modifier(node);
|
||||
}
|
||||
|
||||
mark_subtree_dynamic(context.path);
|
||||
|
||||
if (node.value === true) {
|
||||
// get the binding for node.name and change the binding to state
|
||||
let binding = context.state.scope.get(node.name);
|
||||
|
||||
if (binding) {
|
||||
if (binding.kind !== 'normal') {
|
||||
node.metadata.expression.has_state = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
context.next();
|
||||
|
||||
for (const chunk of get_attribute_chunks(node.value)) {
|
||||
if (chunk.type !== 'ExpressionTag') continue;
|
||||
|
||||
node.metadata.expression.has_state ||= chunk.metadata.expression.has_state;
|
||||
node.metadata.expression.has_call ||= chunk.metadata.expression.has_call;
|
||||
}
|
||||
}
|
||||
}
|
||||
22
node_modules/svelte/src/compiler/phases/2-analyze/visitors/SvelteBody.js
generated
vendored
Normal file
22
node_modules/svelte/src/compiler/phases/2-analyze/visitors/SvelteBody.js
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
/** @import { AST } from '#compiler' */
|
||||
/** @import { Context } from '../types' */
|
||||
import * as e from '../../../errors.js';
|
||||
import { is_event_attribute } from '../../../utils/ast.js';
|
||||
import { disallow_children } from './shared/special-element.js';
|
||||
|
||||
/**
|
||||
* @param {AST.SvelteBody} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function SvelteBody(node, context) {
|
||||
disallow_children(node);
|
||||
for (const attribute of node.attributes) {
|
||||
if (
|
||||
attribute.type === 'SpreadAttribute' ||
|
||||
(attribute.type === 'Attribute' && !is_event_attribute(attribute))
|
||||
) {
|
||||
e.svelte_body_illegal_attribute(attribute);
|
||||
}
|
||||
}
|
||||
context.next();
|
||||
}
|
||||
27
node_modules/svelte/src/compiler/phases/2-analyze/visitors/SvelteBoundary.js
generated
vendored
Normal file
27
node_modules/svelte/src/compiler/phases/2-analyze/visitors/SvelteBoundary.js
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
/** @import { AST } from '#compiler' */
|
||||
/** @import { Context } from '../types' */
|
||||
import * as e from '../../../errors.js';
|
||||
|
||||
const valid = ['onerror', 'failed'];
|
||||
|
||||
/**
|
||||
* @param {AST.SvelteBoundary} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function SvelteBoundary(node, context) {
|
||||
for (const attribute of node.attributes) {
|
||||
if (attribute.type !== 'Attribute' || !valid.includes(attribute.name)) {
|
||||
e.svelte_boundary_invalid_attribute(attribute);
|
||||
}
|
||||
|
||||
if (
|
||||
attribute.value === true ||
|
||||
(Array.isArray(attribute.value) &&
|
||||
(attribute.value.length !== 1 || attribute.value[0].type !== 'ExpressionTag'))
|
||||
) {
|
||||
e.svelte_boundary_invalid_attribute_value(attribute);
|
||||
}
|
||||
}
|
||||
|
||||
context.next();
|
||||
}
|
||||
18
node_modules/svelte/src/compiler/phases/2-analyze/visitors/SvelteComponent.js
generated
vendored
Normal file
18
node_modules/svelte/src/compiler/phases/2-analyze/visitors/SvelteComponent.js
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
/** @import { AST } from '#compiler' */
|
||||
/** @import { Context } from '../types' */
|
||||
import * as w from '../../../warnings.js';
|
||||
import { visit_component } from './shared/component.js';
|
||||
|
||||
/**
|
||||
* @param {AST.SvelteComponent} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function SvelteComponent(node, context) {
|
||||
if (context.state.analysis.runes) {
|
||||
w.svelte_component_deprecated(node);
|
||||
}
|
||||
|
||||
context.visit(node.expression);
|
||||
|
||||
visit_component(node, context);
|
||||
}
|
||||
24
node_modules/svelte/src/compiler/phases/2-analyze/visitors/SvelteDocument.js
generated
vendored
Normal file
24
node_modules/svelte/src/compiler/phases/2-analyze/visitors/SvelteDocument.js
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
/** @import { AST } from '#compiler' */
|
||||
/** @import { Context } from '../types' */
|
||||
import { disallow_children } from './shared/special-element.js';
|
||||
import * as e from '../../../errors.js';
|
||||
import { is_event_attribute } from '../../../utils/ast.js';
|
||||
|
||||
/**
|
||||
* @param {AST.SvelteDocument} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function SvelteDocument(node, context) {
|
||||
disallow_children(node);
|
||||
|
||||
for (const attribute of node.attributes) {
|
||||
if (
|
||||
attribute.type === 'SpreadAttribute' ||
|
||||
(attribute.type === 'Attribute' && !is_event_attribute(attribute))
|
||||
) {
|
||||
e.illegal_element_attribute(attribute, 'svelte:document');
|
||||
}
|
||||
}
|
||||
|
||||
context.next();
|
||||
}
|
||||
66
node_modules/svelte/src/compiler/phases/2-analyze/visitors/SvelteElement.js
generated
vendored
Normal file
66
node_modules/svelte/src/compiler/phases/2-analyze/visitors/SvelteElement.js
generated
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
/** @import { AST } from '#compiler' */
|
||||
/** @import { Context } from '../types' */
|
||||
import { NAMESPACE_MATHML, NAMESPACE_SVG } from '../../../../constants.js';
|
||||
import { is_text_attribute } from '../../../utils/ast.js';
|
||||
import { check_element } from './shared/a11y.js';
|
||||
import { validate_element } from './shared/element.js';
|
||||
import { mark_subtree_dynamic } from './shared/fragment.js';
|
||||
|
||||
/**
|
||||
* @param {AST.SvelteElement} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function SvelteElement(node, context) {
|
||||
validate_element(node, context);
|
||||
check_element(node, context);
|
||||
|
||||
node.metadata.path = [...context.path];
|
||||
context.state.analysis.elements.push(node);
|
||||
|
||||
const xmlns = /** @type {AST.Attribute & { value: [AST.Text] } | undefined} */ (
|
||||
node.attributes.find(
|
||||
(a) => a.type === 'Attribute' && a.name === 'xmlns' && is_text_attribute(a)
|
||||
)
|
||||
);
|
||||
|
||||
if (xmlns) {
|
||||
node.metadata.svg = xmlns.value[0].data === NAMESPACE_SVG;
|
||||
node.metadata.mathml = xmlns.value[0].data === NAMESPACE_MATHML;
|
||||
} else {
|
||||
let i = context.path.length;
|
||||
while (i--) {
|
||||
const ancestor = context.path[i];
|
||||
|
||||
if (
|
||||
ancestor.type === 'Component' ||
|
||||
ancestor.type === 'SvelteComponent' ||
|
||||
ancestor.type === 'SvelteFragment' ||
|
||||
ancestor.type === 'SnippetBlock' ||
|
||||
i === 0
|
||||
) {
|
||||
// Root element, or inside a slot or a snippet -> this resets the namespace, so assume the component namespace
|
||||
node.metadata.svg = context.state.options.namespace === 'svg';
|
||||
node.metadata.mathml = context.state.options.namespace === 'mathml';
|
||||
break;
|
||||
}
|
||||
|
||||
if (ancestor.type === 'SvelteElement' || ancestor.type === 'RegularElement') {
|
||||
node.metadata.svg =
|
||||
ancestor.type === 'RegularElement' && ancestor.name === 'foreignObject'
|
||||
? false
|
||||
: ancestor.metadata.svg;
|
||||
|
||||
node.metadata.mathml =
|
||||
ancestor.type === 'RegularElement' && ancestor.name === 'foreignObject'
|
||||
? false
|
||||
: ancestor.metadata.mathml;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mark_subtree_dynamic(context.path);
|
||||
|
||||
context.next({ ...context.state, parent_element: null });
|
||||
}
|
||||
27
node_modules/svelte/src/compiler/phases/2-analyze/visitors/SvelteFragment.js
generated
vendored
Normal file
27
node_modules/svelte/src/compiler/phases/2-analyze/visitors/SvelteFragment.js
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
/** @import { AST } from '#compiler' */
|
||||
/** @import { Context } from '../types' */
|
||||
import * as e from '../../../errors.js';
|
||||
import { validate_slot_attribute } from './shared/attribute.js';
|
||||
|
||||
/**
|
||||
* @param {AST.SvelteFragment} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function SvelteFragment(node, context) {
|
||||
const parent = context.path.at(-2);
|
||||
if (parent?.type !== 'Component' && parent?.type !== 'SvelteComponent') {
|
||||
e.svelte_fragment_invalid_placement(node);
|
||||
}
|
||||
|
||||
for (const attribute of node.attributes) {
|
||||
if (attribute.type === 'Attribute') {
|
||||
if (attribute.name === 'slot') {
|
||||
validate_slot_attribute(context, attribute);
|
||||
}
|
||||
} else if (attribute.type !== 'LetDirective') {
|
||||
e.svelte_fragment_invalid_attribute(attribute);
|
||||
}
|
||||
}
|
||||
|
||||
context.next({ ...context.state, parent_element: null });
|
||||
}
|
||||
18
node_modules/svelte/src/compiler/phases/2-analyze/visitors/SvelteHead.js
generated
vendored
Normal file
18
node_modules/svelte/src/compiler/phases/2-analyze/visitors/SvelteHead.js
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
/** @import { AST } from '#compiler' */
|
||||
/** @import { Context } from '../types' */
|
||||
import * as e from '../../../errors.js';
|
||||
import { mark_subtree_dynamic } from './shared/fragment.js';
|
||||
|
||||
/**
|
||||
* @param {AST.SvelteHead} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function SvelteHead(node, context) {
|
||||
for (const attribute of node.attributes) {
|
||||
e.svelte_head_illegal_attribute(attribute);
|
||||
}
|
||||
|
||||
mark_subtree_dynamic(context.path);
|
||||
|
||||
context.next();
|
||||
}
|
||||
36
node_modules/svelte/src/compiler/phases/2-analyze/visitors/SvelteSelf.js
generated
vendored
Normal file
36
node_modules/svelte/src/compiler/phases/2-analyze/visitors/SvelteSelf.js
generated
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
/** @import { AST } from '#compiler' */
|
||||
/** @import { Context } from '../types' */
|
||||
import { visit_component } from './shared/component.js';
|
||||
import * as e from '../../../errors.js';
|
||||
import * as w from '../../../warnings.js';
|
||||
import { filename } from '../../../state.js';
|
||||
|
||||
/**
|
||||
* @param {AST.SvelteSelf} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function SvelteSelf(node, context) {
|
||||
const valid = context.path.some(
|
||||
(node) =>
|
||||
node.type === 'IfBlock' ||
|
||||
node.type === 'EachBlock' ||
|
||||
node.type === 'Component' ||
|
||||
node.type === 'SnippetBlock'
|
||||
);
|
||||
|
||||
if (!valid) {
|
||||
e.svelte_self_invalid_placement(node);
|
||||
}
|
||||
|
||||
if (context.state.analysis.runes) {
|
||||
const name = filename === '(unknown)' ? 'Self' : context.state.analysis.name;
|
||||
const basename =
|
||||
filename === '(unknown)'
|
||||
? 'Self.svelte'
|
||||
: /** @type {string} */ (filename.split(/[/\\]/).pop());
|
||||
|
||||
w.svelte_self_deprecated(node, name, basename);
|
||||
}
|
||||
|
||||
visit_component(node, context);
|
||||
}
|
||||
24
node_modules/svelte/src/compiler/phases/2-analyze/visitors/SvelteWindow.js
generated
vendored
Normal file
24
node_modules/svelte/src/compiler/phases/2-analyze/visitors/SvelteWindow.js
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
/** @import { AST } from '#compiler' */
|
||||
/** @import { Context } from '../types' */
|
||||
import { disallow_children } from './shared/special-element.js';
|
||||
import * as e from '../../../errors.js';
|
||||
import { is_event_attribute } from '../../../utils/ast.js';
|
||||
|
||||
/**
|
||||
* @param {AST.SvelteWindow} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function SvelteWindow(node, context) {
|
||||
disallow_children(node);
|
||||
|
||||
for (const attribute of node.attributes) {
|
||||
if (
|
||||
attribute.type === 'SpreadAttribute' ||
|
||||
(attribute.type === 'Attribute' && !is_event_attribute(attribute))
|
||||
) {
|
||||
e.illegal_element_attribute(attribute, 'svelte:window');
|
||||
}
|
||||
}
|
||||
|
||||
context.next();
|
||||
}
|
||||
23
node_modules/svelte/src/compiler/phases/2-analyze/visitors/TaggedTemplateExpression.js
generated
vendored
Normal file
23
node_modules/svelte/src/compiler/phases/2-analyze/visitors/TaggedTemplateExpression.js
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
/** @import { TaggedTemplateExpression, VariableDeclarator } from 'estree' */
|
||||
/** @import { Context } from '../types' */
|
||||
import { is_pure } from './shared/utils.js';
|
||||
|
||||
/**
|
||||
* @param {TaggedTemplateExpression} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function TaggedTemplateExpression(node, context) {
|
||||
if (context.state.expression && !is_pure(node.tag, context)) {
|
||||
context.state.expression.has_call = true;
|
||||
context.state.expression.has_state = true;
|
||||
}
|
||||
|
||||
if (node.tag.type === 'Identifier') {
|
||||
const binding = context.state.scope.get(node.tag.name);
|
||||
|
||||
if (binding !== null) {
|
||||
binding.is_called = true;
|
||||
}
|
||||
}
|
||||
context.next();
|
||||
}
|
||||
20
node_modules/svelte/src/compiler/phases/2-analyze/visitors/Text.js
generated
vendored
Normal file
20
node_modules/svelte/src/compiler/phases/2-analyze/visitors/Text.js
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
/** @import { AST } from '#compiler' */
|
||||
/** @import { Context } from '../types' */
|
||||
import { is_tag_valid_with_parent } from '../../../../html-tree-validation.js';
|
||||
import { regex_not_whitespace } from '../../patterns.js';
|
||||
import * as e from '../../../errors.js';
|
||||
|
||||
/**
|
||||
* @param {AST.Text} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function Text(node, context) {
|
||||
const in_template = context.path.at(-1)?.type === 'Fragment';
|
||||
|
||||
if (in_template && context.state.parent_element && regex_not_whitespace.test(node.data)) {
|
||||
const message = is_tag_valid_with_parent('#text', context.state.parent_element);
|
||||
if (message) {
|
||||
e.node_invalid_placement(node, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
21
node_modules/svelte/src/compiler/phases/2-analyze/visitors/TitleElement.js
generated
vendored
Normal file
21
node_modules/svelte/src/compiler/phases/2-analyze/visitors/TitleElement.js
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
/** @import { AST } from '#compiler' */
|
||||
/** @import { Context } from '../types' */
|
||||
import * as e from '../../../errors.js';
|
||||
|
||||
/**
|
||||
* @param {AST.TitleElement} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function TitleElement(node, context) {
|
||||
for (const attribute of node.attributes) {
|
||||
e.title_illegal_attribute(attribute);
|
||||
}
|
||||
|
||||
for (const child of node.fragment.nodes) {
|
||||
if (child.type !== 'Text' && child.type !== 'ExpressionTag') {
|
||||
e.title_invalid_content(child);
|
||||
}
|
||||
}
|
||||
|
||||
context.next();
|
||||
}
|
||||
14
node_modules/svelte/src/compiler/phases/2-analyze/visitors/TransitionDirective.js
generated
vendored
Normal file
14
node_modules/svelte/src/compiler/phases/2-analyze/visitors/TransitionDirective.js
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
/** @import { AST } from '#compiler' */
|
||||
/** @import { Context } from '../types' */
|
||||
|
||||
import { mark_subtree_dynamic } from './shared/fragment.js';
|
||||
|
||||
/**
|
||||
* @param {AST.TransitionDirective} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function TransitionDirective(node, context) {
|
||||
mark_subtree_dynamic(context.path);
|
||||
|
||||
context.next();
|
||||
}
|
||||
25
node_modules/svelte/src/compiler/phases/2-analyze/visitors/UpdateExpression.js
generated
vendored
Normal file
25
node_modules/svelte/src/compiler/phases/2-analyze/visitors/UpdateExpression.js
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
/** @import { UpdateExpression } from 'estree' */
|
||||
/** @import { Context } from '../types' */
|
||||
import { object } from '../../../utils/ast.js';
|
||||
import { validate_assignment } from './shared/utils.js';
|
||||
|
||||
/**
|
||||
* @param {UpdateExpression} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function UpdateExpression(node, context) {
|
||||
validate_assignment(node, node.argument, context.state);
|
||||
|
||||
if (context.state.reactive_statement) {
|
||||
const id = node.argument.type === 'MemberExpression' ? object(node.argument) : node.argument;
|
||||
if (id?.type === 'Identifier') {
|
||||
const binding = context.state.scope.get(id.name);
|
||||
|
||||
if (binding) {
|
||||
context.state.reactive_statement.assignments.add(binding);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context.next();
|
||||
}
|
||||
12
node_modules/svelte/src/compiler/phases/2-analyze/visitors/UseDirective.js
generated
vendored
Normal file
12
node_modules/svelte/src/compiler/phases/2-analyze/visitors/UseDirective.js
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
/** @import { AST } from '#compiler' */
|
||||
/** @import { Context } from '../types' */
|
||||
import { mark_subtree_dynamic } from './shared/fragment.js';
|
||||
|
||||
/**
|
||||
* @param {AST.UseDirective} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function UseDirective(node, context) {
|
||||
mark_subtree_dynamic(context.path);
|
||||
context.next();
|
||||
}
|
||||
120
node_modules/svelte/src/compiler/phases/2-analyze/visitors/VariableDeclarator.js
generated
vendored
Normal file
120
node_modules/svelte/src/compiler/phases/2-analyze/visitors/VariableDeclarator.js
generated
vendored
Normal file
@@ -0,0 +1,120 @@
|
||||
/** @import { Expression, Identifier, Literal, VariableDeclarator } from 'estree' */
|
||||
/** @import { Binding } from '#compiler' */
|
||||
/** @import { Context } from '../types' */
|
||||
import { get_rune } from '../../scope.js';
|
||||
import { ensure_no_module_import_conflict, validate_identifier_name } from './shared/utils.js';
|
||||
import * as e from '../../../errors.js';
|
||||
import { extract_paths } from '../../../utils/ast.js';
|
||||
import { equal } from '../../../utils/assert.js';
|
||||
|
||||
/**
|
||||
* @param {VariableDeclarator} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function VariableDeclarator(node, context) {
|
||||
ensure_no_module_import_conflict(node, context.state);
|
||||
|
||||
if (context.state.analysis.runes) {
|
||||
const init = node.init;
|
||||
const rune = get_rune(init, context.state.scope);
|
||||
const paths = extract_paths(node.id);
|
||||
|
||||
for (const path of paths) {
|
||||
validate_identifier_name(context.state.scope.get(/** @type {Identifier} */ (path.node).name));
|
||||
}
|
||||
|
||||
// TODO feels like this should happen during scope creation?
|
||||
if (
|
||||
rune === '$state' ||
|
||||
rune === '$state.raw' ||
|
||||
rune === '$derived' ||
|
||||
rune === '$derived.by' ||
|
||||
rune === '$props'
|
||||
) {
|
||||
for (const path of paths) {
|
||||
// @ts-ignore this fails in CI for some insane reason
|
||||
const binding = /** @type {Binding} */ (context.state.scope.get(path.node.name));
|
||||
binding.kind =
|
||||
rune === '$state'
|
||||
? 'state'
|
||||
: rune === '$state.raw'
|
||||
? 'raw_state'
|
||||
: rune === '$derived' || rune === '$derived.by'
|
||||
? 'derived'
|
||||
: path.is_rest
|
||||
? 'rest_prop'
|
||||
: 'prop';
|
||||
}
|
||||
}
|
||||
|
||||
if (rune === '$props') {
|
||||
if (node.id.type !== 'ObjectPattern' && node.id.type !== 'Identifier') {
|
||||
e.props_invalid_identifier(node);
|
||||
}
|
||||
|
||||
context.state.analysis.needs_props = true;
|
||||
|
||||
if (node.id.type === 'Identifier') {
|
||||
const binding = /** @type {Binding} */ (context.state.scope.get(node.id.name));
|
||||
binding.initial = null; // else would be $props()
|
||||
binding.kind = 'rest_prop';
|
||||
} else {
|
||||
equal(node.id.type, 'ObjectPattern');
|
||||
|
||||
for (const property of node.id.properties) {
|
||||
if (property.type !== 'Property') continue;
|
||||
|
||||
if (property.computed) {
|
||||
e.props_invalid_pattern(property);
|
||||
}
|
||||
|
||||
if (property.key.type === 'Identifier' && property.key.name.startsWith('$$')) {
|
||||
e.props_illegal_name(property);
|
||||
}
|
||||
|
||||
const value =
|
||||
property.value.type === 'AssignmentPattern' ? property.value.left : property.value;
|
||||
|
||||
if (value.type !== 'Identifier') {
|
||||
e.props_invalid_pattern(property);
|
||||
}
|
||||
|
||||
const alias =
|
||||
property.key.type === 'Identifier'
|
||||
? property.key.name
|
||||
: String(/** @type {Literal} */ (property.key).value);
|
||||
|
||||
let initial = property.value.type === 'AssignmentPattern' ? property.value.right : null;
|
||||
|
||||
const binding = /** @type {Binding} */ (context.state.scope.get(value.name));
|
||||
binding.prop_alias = alias;
|
||||
|
||||
// rewire initial from $props() to the actual initial value, stripping $bindable() if necessary
|
||||
if (
|
||||
initial?.type === 'CallExpression' &&
|
||||
initial.callee.type === 'Identifier' &&
|
||||
initial.callee.name === '$bindable'
|
||||
) {
|
||||
binding.initial = /** @type {Expression | null} */ (initial.arguments[0] ?? null);
|
||||
binding.kind = 'bindable_prop';
|
||||
} else {
|
||||
binding.initial = initial;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (node.init?.type === 'CallExpression') {
|
||||
const callee = node.init.callee;
|
||||
if (
|
||||
callee.type === 'Identifier' &&
|
||||
(callee.name === '$state' || callee.name === '$derived' || callee.name === '$props') &&
|
||||
context.state.scope.get(callee.name)?.kind !== 'store_sub'
|
||||
) {
|
||||
e.rune_invalid_usage(node.init, callee.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context.next();
|
||||
}
|
||||
1188
node_modules/svelte/src/compiler/phases/2-analyze/visitors/shared/a11y.js
generated
vendored
Normal file
1188
node_modules/svelte/src/compiler/phases/2-analyze/visitors/shared/a11y.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user