fix: vite
This commit is contained in:
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
125
node_modules/svelte/src/compiler/phases/2-analyze/visitors/shared/attribute.js
generated
vendored
Normal file
125
node_modules/svelte/src/compiler/phases/2-analyze/visitors/shared/attribute.js
generated
vendored
Normal file
@@ -0,0 +1,125 @@
|
||||
/** @import { AST } from '#compiler' */
|
||||
/** @import { Context } from '../../types' */
|
||||
import * as e from '../../../../errors.js';
|
||||
import { is_text_attribute } from '../../../../utils/ast.js';
|
||||
import * as w from '../../../../warnings.js';
|
||||
import { is_custom_element_node } from '../../../nodes.js';
|
||||
import { regex_only_whitespaces } from '../../../patterns.js';
|
||||
|
||||
/**
|
||||
* @param {AST.Attribute} attribute
|
||||
*/
|
||||
export function validate_attribute_name(attribute) {
|
||||
if (
|
||||
attribute.name.includes(':') &&
|
||||
!attribute.name.startsWith('xmlns:') &&
|
||||
!attribute.name.startsWith('xlink:') &&
|
||||
!attribute.name.startsWith('xml:')
|
||||
) {
|
||||
w.attribute_illegal_colon(attribute);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {AST.Attribute} attribute
|
||||
* @param {AST.ElementLike} parent
|
||||
*/
|
||||
export function validate_attribute(attribute, parent) {
|
||||
if (
|
||||
Array.isArray(attribute.value) &&
|
||||
attribute.value.length === 1 &&
|
||||
attribute.value[0].type === 'ExpressionTag' &&
|
||||
(parent.type === 'Component' ||
|
||||
parent.type === 'SvelteComponent' ||
|
||||
parent.type === 'SvelteSelf' ||
|
||||
(parent.type === 'RegularElement' && is_custom_element_node(parent)))
|
||||
) {
|
||||
w.attribute_quoted(attribute);
|
||||
}
|
||||
|
||||
if (attribute.value === true || !Array.isArray(attribute.value) || attribute.value.length === 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const is_quoted = attribute.value.at(-1)?.end !== attribute.end;
|
||||
|
||||
if (!is_quoted) {
|
||||
e.attribute_unquoted_sequence(attribute);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Context} context
|
||||
* @param {AST.Attribute} attribute
|
||||
* @param {boolean} is_component
|
||||
*/
|
||||
export function validate_slot_attribute(context, attribute, is_component = false) {
|
||||
const parent = context.path.at(-2);
|
||||
let owner = undefined;
|
||||
|
||||
if (parent?.type === 'SnippetBlock') {
|
||||
if (!is_text_attribute(attribute)) {
|
||||
e.slot_attribute_invalid(attribute);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let i = context.path.length;
|
||||
while (i--) {
|
||||
const ancestor = context.path[i];
|
||||
if (
|
||||
!owner &&
|
||||
(ancestor.type === 'Component' ||
|
||||
ancestor.type === 'SvelteComponent' ||
|
||||
ancestor.type === 'SvelteSelf' ||
|
||||
ancestor.type === 'SvelteElement' ||
|
||||
(ancestor.type === 'RegularElement' && is_custom_element_node(ancestor)))
|
||||
) {
|
||||
owner = ancestor;
|
||||
}
|
||||
}
|
||||
|
||||
if (owner) {
|
||||
if (
|
||||
owner.type === 'Component' ||
|
||||
owner.type === 'SvelteComponent' ||
|
||||
owner.type === 'SvelteSelf'
|
||||
) {
|
||||
if (owner !== parent) {
|
||||
if (!is_component) {
|
||||
e.slot_attribute_invalid_placement(attribute);
|
||||
}
|
||||
} else {
|
||||
if (!is_text_attribute(attribute)) {
|
||||
e.slot_attribute_invalid(attribute);
|
||||
}
|
||||
|
||||
const name = attribute.value[0].data;
|
||||
|
||||
if (context.state.component_slots.has(name)) {
|
||||
e.slot_attribute_duplicate(attribute, name, owner.name);
|
||||
}
|
||||
|
||||
context.state.component_slots.add(name);
|
||||
|
||||
if (name === 'default') {
|
||||
for (const node of owner.fragment.nodes) {
|
||||
if (node.type === 'Text' && regex_only_whitespaces.test(node.data)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (node.type === 'RegularElement' || node.type === 'SvelteFragment') {
|
||||
if (node.attributes.some((a) => a.type === 'Attribute' && a.name === 'slot')) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
e.slot_default_duplicate(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (!is_component) {
|
||||
e.slot_attribute_invalid_placement(attribute);
|
||||
}
|
||||
}
|
||||
160
node_modules/svelte/src/compiler/phases/2-analyze/visitors/shared/component.js
generated
vendored
Normal file
160
node_modules/svelte/src/compiler/phases/2-analyze/visitors/shared/component.js
generated
vendored
Normal file
@@ -0,0 +1,160 @@
|
||||
/** @import { AST } from '#compiler' */
|
||||
/** @import { AnalysisState, Context } from '../../types' */
|
||||
import * as e from '../../../../errors.js';
|
||||
import { get_attribute_expression, is_expression_attribute } from '../../../../utils/ast.js';
|
||||
import { determine_slot } from '../../../../utils/slot.js';
|
||||
import {
|
||||
validate_attribute,
|
||||
validate_attribute_name,
|
||||
validate_slot_attribute
|
||||
} from './attribute.js';
|
||||
import { mark_subtree_dynamic } from './fragment.js';
|
||||
import { is_resolved_snippet } from './snippets.js';
|
||||
|
||||
/**
|
||||
* @param {AST.Component | AST.SvelteComponent | AST.SvelteSelf} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function visit_component(node, context) {
|
||||
node.metadata.path = [...context.path];
|
||||
|
||||
// link this node to all the snippets that it could render, so that we can prune CSS correctly
|
||||
node.metadata.snippets = new Set();
|
||||
|
||||
// 'resolved' means we know which snippets this component might render. if it is `false`,
|
||||
// then `node.metadata.snippets` is populated with every locally defined snippet
|
||||
// once analysis is complete
|
||||
let resolved = true;
|
||||
|
||||
for (const attribute of node.attributes) {
|
||||
if (attribute.type === 'SpreadAttribute' || attribute.type === 'BindDirective') {
|
||||
resolved = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (attribute.type !== 'Attribute' || !is_expression_attribute(attribute)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const expression = get_attribute_expression(attribute);
|
||||
|
||||
// given an attribute like `foo={bar}`, if `bar` resolves to an import or a prop
|
||||
// then we know it doesn't reference a locally defined snippet. if it resolves
|
||||
// to a `{#snippet bar()}` then we know _which_ snippet it resolves to. in all
|
||||
// other cases, we can't know (without much more complex static analysis) which
|
||||
// snippets the component might render, so we treat the component as unresolved
|
||||
if (expression.type === 'Identifier') {
|
||||
const binding = context.state.scope.get(expression.name);
|
||||
|
||||
resolved &&= is_resolved_snippet(binding);
|
||||
|
||||
if (binding?.initial?.type === 'SnippetBlock') {
|
||||
node.metadata.snippets.add(binding.initial);
|
||||
}
|
||||
} else if (expression.type !== 'Literal') {
|
||||
resolved = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (resolved) {
|
||||
for (const child of node.fragment.nodes) {
|
||||
if (child.type === 'SnippetBlock') {
|
||||
node.metadata.snippets.add(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context.state.analysis.snippet_renderers.set(node, resolved);
|
||||
|
||||
mark_subtree_dynamic(context.path);
|
||||
|
||||
for (const attribute of node.attributes) {
|
||||
if (
|
||||
attribute.type !== 'Attribute' &&
|
||||
attribute.type !== 'SpreadAttribute' &&
|
||||
attribute.type !== 'LetDirective' &&
|
||||
attribute.type !== 'OnDirective' &&
|
||||
attribute.type !== 'BindDirective'
|
||||
) {
|
||||
e.component_invalid_directive(attribute);
|
||||
}
|
||||
|
||||
if (
|
||||
attribute.type === 'OnDirective' &&
|
||||
(attribute.modifiers.length > 1 || attribute.modifiers.some((m) => m !== 'once'))
|
||||
) {
|
||||
e.event_handler_invalid_component_modifier(attribute);
|
||||
}
|
||||
|
||||
if (attribute.type === 'Attribute') {
|
||||
if (context.state.analysis.runes) {
|
||||
validate_attribute(attribute, node);
|
||||
|
||||
if (is_expression_attribute(attribute)) {
|
||||
const expression = get_attribute_expression(attribute);
|
||||
if (expression.type === 'SequenceExpression') {
|
||||
let i = /** @type {number} */ (expression.start);
|
||||
while (--i > 0) {
|
||||
const char = context.state.analysis.source[i];
|
||||
if (char === '(') break; // parenthesized sequence expressions are ok
|
||||
if (char === '{') e.attribute_invalid_sequence_expression(expression);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
validate_attribute_name(attribute);
|
||||
|
||||
if (attribute.name === 'slot') {
|
||||
validate_slot_attribute(context, attribute, true);
|
||||
}
|
||||
}
|
||||
|
||||
if (attribute.type === 'BindDirective' && attribute.name !== 'this') {
|
||||
context.state.analysis.uses_component_bindings = true;
|
||||
}
|
||||
}
|
||||
|
||||
// If the component has a slot attribute — `<Foo slot="whatever" .../>` —
|
||||
// then `let:` directives apply to other attributes, instead of just the
|
||||
// top-level contents of the component. Yes, this is very weird.
|
||||
const default_state = determine_slot(node)
|
||||
? context.state
|
||||
: { ...context.state, scope: node.metadata.scopes.default };
|
||||
|
||||
for (const attribute of node.attributes) {
|
||||
context.visit(attribute, attribute.type === 'LetDirective' ? default_state : context.state);
|
||||
}
|
||||
|
||||
/** @type {AST.Comment[]} */
|
||||
let comments = [];
|
||||
|
||||
/** @type {Record<string, AST.Fragment['nodes']>} */
|
||||
const nodes = { default: [] };
|
||||
|
||||
for (const child of node.fragment.nodes) {
|
||||
if (child.type === 'Comment') {
|
||||
comments.push(child);
|
||||
continue;
|
||||
}
|
||||
|
||||
const slot_name = determine_slot(child) ?? 'default';
|
||||
(nodes[slot_name] ??= []).push(...comments, child);
|
||||
|
||||
if (slot_name !== 'default') comments = [];
|
||||
}
|
||||
|
||||
const component_slots = new Set();
|
||||
|
||||
for (const slot_name in nodes) {
|
||||
/** @type {AnalysisState} */
|
||||
const state = {
|
||||
...context.state,
|
||||
scope: node.metadata.scopes[slot_name],
|
||||
parent_element: null,
|
||||
component_slots
|
||||
};
|
||||
|
||||
context.visit({ ...node.fragment, nodes: nodes[slot_name] }, state);
|
||||
}
|
||||
}
|
||||
160
node_modules/svelte/src/compiler/phases/2-analyze/visitors/shared/element.js
generated
vendored
Normal file
160
node_modules/svelte/src/compiler/phases/2-analyze/visitors/shared/element.js
generated
vendored
Normal file
@@ -0,0 +1,160 @@
|
||||
/** @import { AST } from '#compiler' */
|
||||
/** @import { Context } from '../../types' */
|
||||
import { get_attribute_expression, is_expression_attribute } from '../../../../utils/ast.js';
|
||||
import { regex_illegal_attribute_character } from '../../../patterns.js';
|
||||
import * as e from '../../../../errors.js';
|
||||
import * as w from '../../../../warnings.js';
|
||||
import {
|
||||
validate_attribute,
|
||||
validate_attribute_name,
|
||||
validate_slot_attribute
|
||||
} from './attribute.js';
|
||||
|
||||
const EVENT_MODIFIERS = [
|
||||
'preventDefault',
|
||||
'stopPropagation',
|
||||
'stopImmediatePropagation',
|
||||
'capture',
|
||||
'once',
|
||||
'passive',
|
||||
'nonpassive',
|
||||
'self',
|
||||
'trusted'
|
||||
];
|
||||
|
||||
/**
|
||||
* @param {AST.RegularElement | AST.SvelteElement} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function validate_element(node, context) {
|
||||
let has_animate_directive = false;
|
||||
|
||||
/** @type {AST.TransitionDirective | null} */
|
||||
let in_transition = null;
|
||||
|
||||
/** @type {AST.TransitionDirective | null} */
|
||||
let out_transition = null;
|
||||
|
||||
for (const attribute of node.attributes) {
|
||||
if (attribute.type === 'Attribute') {
|
||||
const is_expression = is_expression_attribute(attribute);
|
||||
|
||||
if (context.state.analysis.runes) {
|
||||
validate_attribute(attribute, node);
|
||||
|
||||
if (is_expression) {
|
||||
const expression = get_attribute_expression(attribute);
|
||||
if (expression.type === 'SequenceExpression') {
|
||||
let i = /** @type {number} */ (expression.start);
|
||||
while (--i > 0) {
|
||||
const char = context.state.analysis.source[i];
|
||||
if (char === '(') break; // parenthesized sequence expressions are ok
|
||||
if (char === '{') e.attribute_invalid_sequence_expression(expression);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (regex_illegal_attribute_character.test(attribute.name)) {
|
||||
e.attribute_invalid_name(attribute, attribute.name);
|
||||
}
|
||||
|
||||
if (attribute.name.startsWith('on') && attribute.name.length > 2) {
|
||||
if (!is_expression) {
|
||||
e.attribute_invalid_event_handler(attribute);
|
||||
}
|
||||
|
||||
const value = get_attribute_expression(attribute);
|
||||
if (
|
||||
value.type === 'Identifier' &&
|
||||
value.name === attribute.name &&
|
||||
!context.state.scope.get(value.name)
|
||||
) {
|
||||
w.attribute_global_event_reference(attribute, attribute.name);
|
||||
}
|
||||
}
|
||||
|
||||
if (attribute.name === 'slot') {
|
||||
/** @type {AST.RegularElement | AST.SvelteElement | AST.Component | AST.SvelteComponent | AST.SvelteSelf | undefined} */
|
||||
validate_slot_attribute(context, attribute);
|
||||
}
|
||||
|
||||
if (attribute.name === 'is') {
|
||||
w.attribute_avoid_is(attribute);
|
||||
}
|
||||
|
||||
const correct_name = react_attributes.get(attribute.name);
|
||||
if (correct_name) {
|
||||
w.attribute_invalid_property_name(attribute, attribute.name, correct_name);
|
||||
}
|
||||
|
||||
validate_attribute_name(attribute);
|
||||
} else if (attribute.type === 'AnimateDirective') {
|
||||
const parent = context.path.at(-2);
|
||||
if (parent?.type !== 'EachBlock') {
|
||||
e.animation_invalid_placement(attribute);
|
||||
} else if (!parent.key) {
|
||||
e.animation_missing_key(attribute);
|
||||
} else if (
|
||||
parent.body.nodes.filter(
|
||||
(n) =>
|
||||
n.type !== 'Comment' &&
|
||||
n.type !== 'ConstTag' &&
|
||||
(n.type !== 'Text' || n.data.trim() !== '')
|
||||
).length > 1
|
||||
) {
|
||||
e.animation_invalid_placement(attribute);
|
||||
}
|
||||
|
||||
if (has_animate_directive) {
|
||||
e.animation_duplicate(attribute);
|
||||
} else {
|
||||
has_animate_directive = true;
|
||||
}
|
||||
} else if (attribute.type === 'TransitionDirective') {
|
||||
const existing = /** @type {AST.TransitionDirective | null} */ (
|
||||
(attribute.intro && in_transition) || (attribute.outro && out_transition)
|
||||
);
|
||||
|
||||
if (existing) {
|
||||
const a = existing.intro ? (existing.outro ? 'transition' : 'in') : 'out';
|
||||
const b = attribute.intro ? (attribute.outro ? 'transition' : 'in') : 'out';
|
||||
|
||||
if (a === b) {
|
||||
e.transition_duplicate(attribute, a);
|
||||
} else {
|
||||
e.transition_conflict(attribute, a, b);
|
||||
}
|
||||
}
|
||||
|
||||
if (attribute.intro) in_transition = attribute;
|
||||
if (attribute.outro) out_transition = attribute;
|
||||
} else if (attribute.type === 'OnDirective') {
|
||||
let has_passive_modifier = false;
|
||||
let conflicting_passive_modifier = '';
|
||||
for (const modifier of attribute.modifiers) {
|
||||
if (!EVENT_MODIFIERS.includes(modifier)) {
|
||||
const list = `${EVENT_MODIFIERS.slice(0, -1).join(', ')} or ${EVENT_MODIFIERS.at(-1)}`;
|
||||
e.event_handler_invalid_modifier(attribute, list);
|
||||
}
|
||||
if (modifier === 'passive') {
|
||||
has_passive_modifier = true;
|
||||
} else if (modifier === 'nonpassive' || modifier === 'preventDefault') {
|
||||
conflicting_passive_modifier = modifier;
|
||||
}
|
||||
if (has_passive_modifier && conflicting_passive_modifier) {
|
||||
e.event_handler_invalid_modifier_combination(
|
||||
attribute,
|
||||
'passive',
|
||||
conflicting_passive_modifier
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const react_attributes = new Map([
|
||||
['className', 'class'],
|
||||
['htmlFor', 'for']
|
||||
]);
|
||||
15
node_modules/svelte/src/compiler/phases/2-analyze/visitors/shared/fragment.js
generated
vendored
Normal file
15
node_modules/svelte/src/compiler/phases/2-analyze/visitors/shared/fragment.js
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
/** @import { AST } from '#compiler' */
|
||||
|
||||
/**
|
||||
* @param {AST.SvelteNode[]} path
|
||||
*/
|
||||
export function mark_subtree_dynamic(path) {
|
||||
let i = path.length;
|
||||
while (i--) {
|
||||
const node = path[i];
|
||||
if (node.type === 'Fragment') {
|
||||
if (node.metadata.dynamic) return;
|
||||
node.metadata.dynamic = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
21
node_modules/svelte/src/compiler/phases/2-analyze/visitors/shared/function.js
generated
vendored
Normal file
21
node_modules/svelte/src/compiler/phases/2-analyze/visitors/shared/function.js
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
/** @import { ArrowFunctionExpression, FunctionDeclaration, FunctionExpression } from 'estree' */
|
||||
/** @import { Context } from '../../types' */
|
||||
|
||||
/**
|
||||
* @param {ArrowFunctionExpression | FunctionExpression | FunctionDeclaration} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function visit_function(node, context) {
|
||||
// TODO retire this in favour of a more general solution based on bindings
|
||||
node.metadata = {
|
||||
hoisted: false,
|
||||
hoisted_params: [],
|
||||
scope: context.state.scope
|
||||
};
|
||||
|
||||
context.next({
|
||||
...context.state,
|
||||
function_depth: context.state.function_depth + 1,
|
||||
expression: null
|
||||
});
|
||||
}
|
||||
17
node_modules/svelte/src/compiler/phases/2-analyze/visitors/shared/snippets.js
generated
vendored
Normal file
17
node_modules/svelte/src/compiler/phases/2-analyze/visitors/shared/snippets.js
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
/** @import { Binding } from '#compiler' */
|
||||
|
||||
/**
|
||||
* Returns `true` if a binding unambiguously resolves to a specific
|
||||
* snippet declaration, or is external to the current component
|
||||
* @param {Binding | null} binding
|
||||
*/
|
||||
export function is_resolved_snippet(binding) {
|
||||
return (
|
||||
!binding ||
|
||||
binding.declaration_kind === 'import' ||
|
||||
binding.kind === 'prop' ||
|
||||
binding.kind === 'rest_prop' ||
|
||||
binding.kind === 'bindable_prop' ||
|
||||
binding?.initial?.type === 'SnippetBlock'
|
||||
);
|
||||
}
|
||||
16
node_modules/svelte/src/compiler/phases/2-analyze/visitors/shared/special-element.js
generated
vendored
Normal file
16
node_modules/svelte/src/compiler/phases/2-analyze/visitors/shared/special-element.js
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
/** @import { AST } from '#compiler' */
|
||||
import * as e from '../../../../errors.js';
|
||||
|
||||
/**
|
||||
* @param {AST.SvelteBody | AST.SvelteDocument | AST.SvelteOptionsRaw | AST.SvelteWindow} node
|
||||
*/
|
||||
export function disallow_children(node) {
|
||||
const { nodes } = node.fragment;
|
||||
|
||||
if (nodes.length > 0) {
|
||||
const first = nodes[0];
|
||||
const last = nodes[nodes.length - 1];
|
||||
|
||||
e.svelte_meta_invalid_content({ start: first.start, end: last.end }, node.name);
|
||||
}
|
||||
}
|
||||
284
node_modules/svelte/src/compiler/phases/2-analyze/visitors/shared/utils.js
generated
vendored
Normal file
284
node_modules/svelte/src/compiler/phases/2-analyze/visitors/shared/utils.js
generated
vendored
Normal file
@@ -0,0 +1,284 @@
|
||||
/** @import { AssignmentExpression, Expression, Literal, Node, Pattern, PrivateIdentifier, Super, UpdateExpression, VariableDeclarator } from 'estree' */
|
||||
/** @import { AST, Binding } from '#compiler' */
|
||||
/** @import { AnalysisState, Context } from '../../types' */
|
||||
/** @import { Scope } from '../../../scope' */
|
||||
/** @import { NodeLike } from '../../../../errors.js' */
|
||||
import * as e from '../../../../errors.js';
|
||||
import { extract_identifiers } from '../../../../utils/ast.js';
|
||||
import * as w from '../../../../warnings.js';
|
||||
import * as b from '../../../../utils/builders.js';
|
||||
import { get_rune } from '../../../scope.js';
|
||||
|
||||
/**
|
||||
* @param {AssignmentExpression | UpdateExpression} node
|
||||
* @param {Pattern | Expression} argument
|
||||
* @param {AnalysisState} state
|
||||
*/
|
||||
export function validate_assignment(node, argument, state) {
|
||||
validate_no_const_assignment(node, argument, state.scope, false);
|
||||
|
||||
if (argument.type === 'Identifier') {
|
||||
const binding = state.scope.get(argument.name);
|
||||
|
||||
if (state.analysis.runes) {
|
||||
if (binding?.kind === 'derived') {
|
||||
e.constant_assignment(node, 'derived state');
|
||||
}
|
||||
|
||||
if (binding?.kind === 'each') {
|
||||
e.each_item_invalid_assignment(node);
|
||||
}
|
||||
}
|
||||
|
||||
if (binding?.kind === 'snippet') {
|
||||
e.snippet_parameter_assignment(node);
|
||||
}
|
||||
}
|
||||
|
||||
let object = /** @type {Expression | Super} */ (argument);
|
||||
|
||||
/** @type {Expression | PrivateIdentifier | null} */
|
||||
let property = null;
|
||||
|
||||
while (object.type === 'MemberExpression') {
|
||||
property = object.property;
|
||||
object = object.object;
|
||||
}
|
||||
|
||||
if (object.type === 'ThisExpression' && property?.type === 'PrivateIdentifier') {
|
||||
if (state.private_derived_state.includes(property.name)) {
|
||||
e.constant_assignment(node, 'derived state');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {NodeLike} node
|
||||
* @param {Pattern | Expression} argument
|
||||
* @param {Scope} scope
|
||||
* @param {boolean} is_binding
|
||||
*/
|
||||
export function validate_no_const_assignment(node, argument, scope, is_binding) {
|
||||
if (argument.type === 'ArrayPattern') {
|
||||
for (const element of argument.elements) {
|
||||
if (element) {
|
||||
validate_no_const_assignment(node, element, scope, is_binding);
|
||||
}
|
||||
}
|
||||
} else if (argument.type === 'ObjectPattern') {
|
||||
for (const element of argument.properties) {
|
||||
if (element.type === 'Property') {
|
||||
validate_no_const_assignment(node, element.value, scope, is_binding);
|
||||
}
|
||||
}
|
||||
} else if (argument.type === 'Identifier') {
|
||||
const binding = scope.get(argument.name);
|
||||
if (
|
||||
binding?.kind === 'derived' ||
|
||||
binding?.declaration_kind === 'import' ||
|
||||
(binding?.declaration_kind === 'const' && binding.kind !== 'each')
|
||||
) {
|
||||
// e.invalid_const_assignment(
|
||||
// node,
|
||||
// is_binding,
|
||||
// // This takes advantage of the fact that we don't assign initial for let directives and then/catch variables.
|
||||
// // If we start doing that, we need another property on the binding to differentiate, or give up on the more precise error message.
|
||||
// binding.kind !== 'state' &&
|
||||
// binding.kind !== 'raw_state' &&
|
||||
// (binding.kind !== 'normal' || !binding.initial)
|
||||
// );
|
||||
|
||||
// TODO have a more specific error message for assignments to things like `{:then foo}`
|
||||
const thing =
|
||||
binding.declaration_kind === 'import'
|
||||
? 'import'
|
||||
: binding.kind === 'derived'
|
||||
? 'derived state'
|
||||
: 'constant';
|
||||
|
||||
if (is_binding) {
|
||||
e.constant_binding(node, thing);
|
||||
} else {
|
||||
e.constant_assignment(node, thing);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that the opening of a control flow block is `{` immediately followed by the expected character.
|
||||
* In legacy mode whitespace is allowed inbetween. TODO remove once legacy mode is gone and move this into parser instead.
|
||||
* @param {{start: number; end: number}} node
|
||||
* @param {AnalysisState} state
|
||||
* @param {string} expected
|
||||
*/
|
||||
export function validate_opening_tag(node, state, expected) {
|
||||
if (state.analysis.source[node.start + 1] !== expected) {
|
||||
// avoid a sea of red and only mark the first few characters
|
||||
e.block_unexpected_character({ start: node.start, end: node.start + 5 }, expected);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {AST.Fragment | null | undefined} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function validate_block_not_empty(node, context) {
|
||||
if (!node) return;
|
||||
// Assumption: If the block has zero elements, someone's in the middle of typing it out,
|
||||
// so don't warn in that case because it would be distracting.
|
||||
if (node.nodes.length === 1 && node.nodes[0].type === 'Text' && !node.nodes[0].raw.trim()) {
|
||||
w.block_empty(node.nodes[0]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {VariableDeclarator} node
|
||||
* @param {AnalysisState} state
|
||||
*/
|
||||
export function ensure_no_module_import_conflict(node, state) {
|
||||
const ids = extract_identifiers(node.id);
|
||||
for (const id of ids) {
|
||||
if (
|
||||
state.ast_type === 'instance' &&
|
||||
state.scope === state.analysis.instance.scope &&
|
||||
state.analysis.module.scope.get(id.name)?.declaration_kind === 'import'
|
||||
) {
|
||||
// TODO fix the message here
|
||||
e.declaration_duplicate_module_import(node.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A 'safe' identifier means that the `foo` in `foo.bar` or `foo()` will not
|
||||
* call functions that require component context to exist
|
||||
* @param {Expression | Super} expression
|
||||
* @param {Scope} scope
|
||||
*/
|
||||
export function is_safe_identifier(expression, scope) {
|
||||
let node = expression;
|
||||
while (node.type === 'MemberExpression') node = node.object;
|
||||
|
||||
if (node.type !== 'Identifier') return false;
|
||||
|
||||
const binding = scope.get(node.name);
|
||||
if (!binding) return true;
|
||||
|
||||
if (binding.kind === 'store_sub') {
|
||||
return is_safe_identifier({ name: node.name.slice(1), type: 'Identifier' }, scope);
|
||||
}
|
||||
|
||||
return (
|
||||
binding.declaration_kind !== 'import' &&
|
||||
binding.kind !== 'prop' &&
|
||||
binding.kind !== 'bindable_prop' &&
|
||||
binding.kind !== 'rest_prop'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Expression | Literal | Super} node
|
||||
* @param {Context} context
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function is_pure(node, context) {
|
||||
if (node.type === 'Literal') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (node.type === 'CallExpression') {
|
||||
if (!is_pure(node.callee, context)) {
|
||||
return false;
|
||||
}
|
||||
for (let arg of node.arguments) {
|
||||
if (!is_pure(arg.type === 'SpreadElement' ? arg.argument : arg, context)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (node.type !== 'Identifier' && node.type !== 'MemberExpression') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (get_rune(b.call(node), context.state.scope) === '$effect.tracking') {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @type {Expression | Super | null} */
|
||||
let left = node;
|
||||
while (left.type === 'MemberExpression') {
|
||||
left = left.object;
|
||||
}
|
||||
|
||||
if (!left) return false;
|
||||
|
||||
if (left.type === 'Identifier') {
|
||||
const binding = context.state.scope.get(left.name);
|
||||
if (binding === null) return true; // globals are assumed to be safe
|
||||
} else if (is_pure(left, context)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO add more cases (safe Svelte imports, etc)
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the name is valid, which it is when it's not starting with (or is) a dollar sign or if it's a function parameter.
|
||||
* The second argument is the depth of the scope, which is there for backwards compatibility reasons: In Svelte 4, you
|
||||
* were allowed to define `$`-prefixed variables anywhere below the top level of components. Once legacy mode is gone, this
|
||||
* argument can be removed / the call sites adjusted accordingly.
|
||||
* @param {Binding | null} binding
|
||||
* @param {number | undefined} [function_depth]
|
||||
*/
|
||||
export function validate_identifier_name(binding, function_depth) {
|
||||
if (!binding) return;
|
||||
|
||||
const declaration_kind = binding.declaration_kind;
|
||||
|
||||
if (
|
||||
declaration_kind !== 'synthetic' &&
|
||||
declaration_kind !== 'param' &&
|
||||
declaration_kind !== 'rest_param' &&
|
||||
(!function_depth || function_depth <= 1)
|
||||
) {
|
||||
const node = binding.node;
|
||||
|
||||
if (node.name === '$') {
|
||||
e.dollar_binding_invalid(node);
|
||||
} else if (
|
||||
node.name.startsWith('$') &&
|
||||
// import type { $Type } from "" - these are normally already filtered out,
|
||||
// but for the migration they aren't, and throwing here is preventing the migration to complete
|
||||
// TODO -> once migration script is gone we can remove this check
|
||||
!(
|
||||
binding.initial?.type === 'ImportDeclaration' &&
|
||||
/** @type {any} */ (binding.initial).importKind === 'type'
|
||||
)
|
||||
) {
|
||||
e.dollar_prefix_invalid(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the exported name is not a derived or reassigned state variable.
|
||||
* @param {Node} node
|
||||
* @param {Scope} scope
|
||||
* @param {string} name
|
||||
*/
|
||||
export function validate_export(node, scope, name) {
|
||||
const binding = scope.get(name);
|
||||
if (!binding) return;
|
||||
|
||||
if (binding.kind === 'derived') {
|
||||
e.derived_invalid_export(node);
|
||||
}
|
||||
|
||||
if ((binding.kind === 'state' || binding.kind === 'raw_state') && binding.reassigned) {
|
||||
e.state_invalid_export(node);
|
||||
}
|
||||
}
|
||||
682
node_modules/svelte/src/compiler/phases/3-transform/client/transform-client.js
generated
vendored
Normal file
682
node_modules/svelte/src/compiler/phases/3-transform/client/transform-client.js
generated
vendored
Normal file
@@ -0,0 +1,682 @@
|
||||
/** @import * as ESTree from 'estree' */
|
||||
/** @import { AST, ValidatedCompileOptions, ValidatedModuleCompileOptions } from '#compiler' */
|
||||
/** @import { ComponentAnalysis, Analysis } from '../../types' */
|
||||
/** @import { Visitors, ComponentClientTransformState, ClientTransformState } from './types' */
|
||||
import { walk } from 'zimmerframe';
|
||||
import * as b from '../../../utils/builders.js';
|
||||
import { build_getter, is_state_source } from './utils.js';
|
||||
import { render_stylesheet } from '../css/index.js';
|
||||
import { dev, filename } from '../../../state.js';
|
||||
import { AnimateDirective } from './visitors/AnimateDirective.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 { BinaryExpression } from './visitors/BinaryExpression.js';
|
||||
import { BindDirective } from './visitors/BindDirective.js';
|
||||
import { BlockStatement } from './visitors/BlockStatement.js';
|
||||
import { BreakStatement } from './visitors/BreakStatement.js';
|
||||
import { CallExpression } from './visitors/CallExpression.js';
|
||||
import { ClassBody } from './visitors/ClassBody.js';
|
||||
import { Comment } from './visitors/Comment.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 { ExportNamedDeclaration } from './visitors/ExportNamedDeclaration.js';
|
||||
import { ExpressionStatement } from './visitors/ExpressionStatement.js';
|
||||
import { Fragment } from './visitors/Fragment.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 { OnDirective } from './visitors/OnDirective.js';
|
||||
import { Program } from './visitors/Program.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 { 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 { SvelteBoundary } from './visitors/SvelteBoundary.js';
|
||||
import { SvelteHead } from './visitors/SvelteHead.js';
|
||||
import { SvelteSelf } from './visitors/SvelteSelf.js';
|
||||
import { SvelteWindow } from './visitors/SvelteWindow.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 { VariableDeclaration } from './visitors/VariableDeclaration.js';
|
||||
|
||||
/** @type {Visitors} */
|
||||
const visitors = {
|
||||
_: function set_scope(node, { next, state }) {
|
||||
const scope = state.scopes.get(node);
|
||||
|
||||
if (scope && scope !== state.scope) {
|
||||
const transform = { ...state.transform };
|
||||
|
||||
for (const [name, binding] of scope.declarations) {
|
||||
if (
|
||||
binding.kind === 'normal' ||
|
||||
// Reads of `$state(...)` declarations are not
|
||||
// transformed if they are never reassigned
|
||||
(binding.kind === 'state' && !is_state_source(binding, state.analysis))
|
||||
) {
|
||||
delete transform[name];
|
||||
}
|
||||
}
|
||||
|
||||
next({ ...state, transform, scope });
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
},
|
||||
AnimateDirective,
|
||||
ArrowFunctionExpression,
|
||||
AssignmentExpression,
|
||||
Attribute,
|
||||
AwaitBlock,
|
||||
BinaryExpression,
|
||||
BindDirective,
|
||||
BlockStatement,
|
||||
BreakStatement,
|
||||
CallExpression,
|
||||
ClassBody,
|
||||
Comment,
|
||||
Component,
|
||||
ConstTag,
|
||||
DebugTag,
|
||||
EachBlock,
|
||||
ExportNamedDeclaration,
|
||||
ExpressionStatement,
|
||||
Fragment,
|
||||
FunctionDeclaration,
|
||||
FunctionExpression,
|
||||
HtmlTag,
|
||||
Identifier,
|
||||
IfBlock,
|
||||
ImportDeclaration,
|
||||
KeyBlock,
|
||||
LabeledStatement,
|
||||
LetDirective,
|
||||
MemberExpression,
|
||||
OnDirective,
|
||||
Program,
|
||||
RegularElement,
|
||||
RenderTag,
|
||||
SlotElement,
|
||||
SnippetBlock,
|
||||
SpreadAttribute,
|
||||
SvelteBody,
|
||||
SvelteComponent,
|
||||
SvelteDocument,
|
||||
SvelteElement,
|
||||
SvelteFragment,
|
||||
SvelteBoundary,
|
||||
SvelteHead,
|
||||
SvelteSelf,
|
||||
SvelteWindow,
|
||||
TitleElement,
|
||||
TransitionDirective,
|
||||
UpdateExpression,
|
||||
UseDirective,
|
||||
VariableDeclaration
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {ComponentAnalysis} analysis
|
||||
* @param {ValidatedCompileOptions} options
|
||||
* @returns {ESTree.Program}
|
||||
*/
|
||||
export function client_component(analysis, options) {
|
||||
/** @type {ComponentClientTransformState} */
|
||||
const state = {
|
||||
analysis,
|
||||
options,
|
||||
scope: analysis.module.scope,
|
||||
scopes: analysis.module.scopes,
|
||||
is_instance: false,
|
||||
hoisted: [b.import_all('$', 'svelte/internal/client')],
|
||||
node: /** @type {any} */ (null), // populated by the root node
|
||||
legacy_reactive_imports: [],
|
||||
legacy_reactive_statements: new Map(),
|
||||
metadata: {
|
||||
context: {
|
||||
template_needs_import_node: false,
|
||||
template_contains_script_tag: false
|
||||
},
|
||||
namespace: options.namespace,
|
||||
bound_contenteditable: false
|
||||
},
|
||||
events: new Set(),
|
||||
preserve_whitespace: options.preserveWhitespace,
|
||||
public_state: new Map(),
|
||||
private_state: new Map(),
|
||||
transform: {},
|
||||
in_constructor: false,
|
||||
instance_level_snippets: [],
|
||||
module_level_snippets: [],
|
||||
|
||||
// these are set inside the `Fragment` visitor, and cannot be used until then
|
||||
init: /** @type {any} */ (null),
|
||||
update: /** @type {any} */ (null),
|
||||
expressions: /** @type {any} */ (null),
|
||||
after_update: /** @type {any} */ (null),
|
||||
template: /** @type {any} */ (null),
|
||||
locations: /** @type {any} */ (null)
|
||||
};
|
||||
|
||||
const module = /** @type {ESTree.Program} */ (
|
||||
walk(/** @type {AST.SvelteNode} */ (analysis.module.ast), state, visitors)
|
||||
);
|
||||
|
||||
const instance_state = {
|
||||
...state,
|
||||
transform: { ...state.transform },
|
||||
scope: analysis.instance.scope,
|
||||
scopes: analysis.instance.scopes,
|
||||
is_instance: true
|
||||
};
|
||||
|
||||
const instance = /** @type {ESTree.Program} */ (
|
||||
walk(/** @type {AST.SvelteNode} */ (analysis.instance.ast), instance_state, visitors)
|
||||
);
|
||||
|
||||
const template = /** @type {ESTree.Program} */ (
|
||||
walk(
|
||||
/** @type {AST.SvelteNode} */ (analysis.template.ast),
|
||||
{
|
||||
...state,
|
||||
transform: instance_state.transform,
|
||||
scope: analysis.instance.scope,
|
||||
scopes: analysis.template.scopes
|
||||
},
|
||||
visitors
|
||||
)
|
||||
);
|
||||
|
||||
module.body.unshift(...state.legacy_reactive_imports);
|
||||
|
||||
/** @type {ESTree.Statement[]} */
|
||||
const store_setup = [];
|
||||
|
||||
/** @type {ESTree.VariableDeclaration[]} */
|
||||
const legacy_reactive_declarations = [];
|
||||
|
||||
let needs_store_cleanup = false;
|
||||
|
||||
for (const [name, binding] of analysis.instance.scope.declarations) {
|
||||
if (binding.kind === 'legacy_reactive') {
|
||||
legacy_reactive_declarations.push(
|
||||
b.const(name, b.call('$.mutable_state', undefined, analysis.immutable ? b.true : undefined))
|
||||
);
|
||||
}
|
||||
if (binding.kind === 'store_sub') {
|
||||
if (store_setup.length === 0) {
|
||||
needs_store_cleanup = true;
|
||||
store_setup.push(
|
||||
b.const(b.array_pattern([b.id('$$stores'), b.id('$$cleanup')]), b.call('$.setup_stores'))
|
||||
);
|
||||
}
|
||||
|
||||
// We're creating an arrow function that gets the store value which minifies better for two or more references
|
||||
const store_reference = build_getter(b.id(name.slice(1)), instance_state);
|
||||
const store_get = b.call('$.store_get', store_reference, b.literal(name), b.id('$$stores'));
|
||||
store_setup.push(
|
||||
b.const(
|
||||
binding.node,
|
||||
dev
|
||||
? b.thunk(
|
||||
b.sequence([
|
||||
b.call('$.validate_store', store_reference, b.literal(name.slice(1))),
|
||||
store_get
|
||||
])
|
||||
)
|
||||
: b.thunk(store_get)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for (const [node] of analysis.reactive_statements) {
|
||||
const statement = [...state.legacy_reactive_statements].find(([n]) => n === node);
|
||||
if (statement === undefined) {
|
||||
throw new Error('Could not find reactive statement');
|
||||
}
|
||||
instance.body.push(statement[1]);
|
||||
}
|
||||
|
||||
if (analysis.reactive_statements.size > 0) {
|
||||
instance.body.push(b.stmt(b.call('$.legacy_pre_effect_reset')));
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to store the group nodes
|
||||
* @type {ESTree.VariableDeclaration[]}
|
||||
*/
|
||||
const group_binding_declarations = [];
|
||||
for (const group of analysis.binding_groups.values()) {
|
||||
group_binding_declarations.push(b.const(group.name, b.array([])));
|
||||
}
|
||||
|
||||
/** @type {Array<ESTree.Property | ESTree.SpreadElement>} */
|
||||
const component_returned_object = analysis.exports.flatMap(({ name, alias }) => {
|
||||
const binding = instance_state.scope.get(name);
|
||||
const expression = build_getter(b.id(name), instance_state);
|
||||
const getter = b.get(alias ?? name, [b.return(expression)]);
|
||||
|
||||
if (expression.type === 'Identifier') {
|
||||
if (binding?.declaration_kind === 'let' || binding?.declaration_kind === 'var') {
|
||||
return [
|
||||
getter,
|
||||
b.set(alias ?? name, [b.stmt(b.assignment('=', expression, b.id('$$value')))])
|
||||
];
|
||||
} else if (!dev) {
|
||||
return b.init(alias ?? name, expression);
|
||||
}
|
||||
}
|
||||
|
||||
if (binding?.kind === 'prop' || binding?.kind === 'bindable_prop') {
|
||||
return [getter, b.set(alias ?? name, [b.stmt(b.call(name, b.id('$$value')))])];
|
||||
}
|
||||
|
||||
if (binding?.kind === 'state' || binding?.kind === 'raw_state') {
|
||||
const value = binding.kind === 'state' ? b.call('$.proxy', b.id('$$value')) : b.id('$$value');
|
||||
return [getter, b.set(alias ?? name, [b.stmt(b.call('$.set', b.id(name), value))])];
|
||||
}
|
||||
|
||||
return getter;
|
||||
});
|
||||
|
||||
const properties = [...analysis.instance.scope.declarations].filter(
|
||||
([name, binding]) =>
|
||||
(binding.kind === 'prop' || binding.kind === 'bindable_prop') && !name.startsWith('$$')
|
||||
);
|
||||
|
||||
if (analysis.accessors) {
|
||||
for (const [name, binding] of properties) {
|
||||
const key = binding.prop_alias ?? name;
|
||||
|
||||
const getter = b.get(key, [b.return(b.call(b.id(name)))]);
|
||||
|
||||
const setter = b.set(key, [
|
||||
b.stmt(b.call(b.id(name), b.id('$$value'))),
|
||||
b.stmt(b.call('$.flush_sync'))
|
||||
]);
|
||||
|
||||
if (analysis.runes && binding.initial) {
|
||||
// turn `set foo($$value)` into `set foo($$value = expression)`
|
||||
setter.value.params[0] = {
|
||||
type: 'AssignmentPattern',
|
||||
left: b.id('$$value'),
|
||||
right: /** @type {ESTree.Expression} */ (binding.initial)
|
||||
};
|
||||
}
|
||||
|
||||
component_returned_object.push(getter, setter);
|
||||
}
|
||||
}
|
||||
|
||||
if (options.compatibility.componentApi === 4) {
|
||||
component_returned_object.push(
|
||||
b.init('$set', b.id('$.update_legacy_props')),
|
||||
b.init(
|
||||
'$on',
|
||||
b.arrow(
|
||||
[b.id('$$event_name'), b.id('$$event_cb')],
|
||||
b.call(
|
||||
'$.add_legacy_event_listener',
|
||||
b.id('$$props'),
|
||||
b.id('$$event_name'),
|
||||
b.id('$$event_cb')
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
} else if (dev) {
|
||||
component_returned_object.push(b.spread(b.call(b.id('$.legacy_api'))));
|
||||
}
|
||||
|
||||
const push_args = [b.id('$$props'), b.literal(analysis.runes)];
|
||||
if (dev) push_args.push(b.id(analysis.name));
|
||||
|
||||
const component_block = b.block([
|
||||
...store_setup,
|
||||
...legacy_reactive_declarations,
|
||||
...group_binding_declarations,
|
||||
...state.instance_level_snippets,
|
||||
.../** @type {ESTree.Statement[]} */ (instance.body),
|
||||
analysis.runes || !analysis.needs_context
|
||||
? b.empty
|
||||
: b.stmt(b.call('$.init', analysis.immutable ? b.true : undefined)),
|
||||
.../** @type {ESTree.Statement[]} */ (template.body)
|
||||
]);
|
||||
|
||||
if (!analysis.runes) {
|
||||
// Bind static exports to props so that people can access them with bind:x
|
||||
for (const { name, alias } of analysis.exports) {
|
||||
component_block.body.push(
|
||||
b.stmt(
|
||||
b.call(
|
||||
'$.bind_prop',
|
||||
b.id('$$props'),
|
||||
b.literal(alias ?? name),
|
||||
build_getter(b.id(name), instance_state)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (analysis.css.ast !== null && analysis.inject_styles) {
|
||||
const hash = b.literal(analysis.css.hash);
|
||||
const code = b.literal(render_stylesheet(analysis.source, analysis, options).code);
|
||||
|
||||
state.hoisted.push(b.const('$$css', b.object([b.init('hash', hash), b.init('code', code)])));
|
||||
|
||||
component_block.body.unshift(
|
||||
b.stmt(b.call('$.append_styles', b.id('$$anchor'), b.id('$$css')))
|
||||
);
|
||||
}
|
||||
|
||||
const should_inject_context =
|
||||
dev ||
|
||||
analysis.needs_context ||
|
||||
analysis.reactive_statements.size > 0 ||
|
||||
component_returned_object.length > 0;
|
||||
|
||||
// we want the cleanup function for the stores to run as the very last thing
|
||||
// so that it can effectively clean up the store subscription even after the user effects runs
|
||||
if (should_inject_context) {
|
||||
component_block.body.unshift(b.stmt(b.call('$.push', ...push_args)));
|
||||
|
||||
let to_push;
|
||||
|
||||
if (component_returned_object.length > 0) {
|
||||
let pop_call = b.call('$.pop', b.object(component_returned_object));
|
||||
to_push = needs_store_cleanup ? b.var('$$pop', pop_call) : b.return(pop_call);
|
||||
} else {
|
||||
to_push = b.stmt(b.call('$.pop'));
|
||||
}
|
||||
|
||||
component_block.body.push(to_push);
|
||||
}
|
||||
|
||||
if (needs_store_cleanup) {
|
||||
component_block.body.push(b.stmt(b.call('$$cleanup')));
|
||||
if (component_returned_object.length > 0) {
|
||||
component_block.body.push(b.return(b.id('$$pop')));
|
||||
}
|
||||
}
|
||||
|
||||
if (analysis.uses_rest_props) {
|
||||
const named_props = analysis.exports.map(({ name, alias }) => alias ?? name);
|
||||
for (const [name, binding] of analysis.instance.scope.declarations) {
|
||||
if (binding.kind === 'bindable_prop') named_props.push(binding.prop_alias ?? name);
|
||||
}
|
||||
|
||||
component_block.body.unshift(
|
||||
b.const(
|
||||
'$$restProps',
|
||||
b.call(
|
||||
'$.legacy_rest_props',
|
||||
b.id('$$sanitized_props'),
|
||||
b.array(named_props.map((name) => b.literal(name)))
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (analysis.uses_props || analysis.uses_rest_props) {
|
||||
const to_remove = [
|
||||
b.literal('children'),
|
||||
b.literal('$$slots'),
|
||||
b.literal('$$events'),
|
||||
b.literal('$$legacy')
|
||||
];
|
||||
if (analysis.custom_element) {
|
||||
to_remove.push(b.literal('$$host'));
|
||||
}
|
||||
|
||||
component_block.body.unshift(
|
||||
b.const(
|
||||
'$$sanitized_props',
|
||||
b.call('$.legacy_rest_props', b.id('$$props'), b.array(to_remove))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (analysis.uses_slots) {
|
||||
component_block.body.unshift(b.const('$$slots', b.call('$.sanitize_slots', b.id('$$props'))));
|
||||
}
|
||||
|
||||
let should_inject_props =
|
||||
should_inject_context ||
|
||||
analysis.needs_props ||
|
||||
analysis.uses_props ||
|
||||
analysis.uses_rest_props ||
|
||||
analysis.uses_slots ||
|
||||
analysis.slot_names.size > 0;
|
||||
|
||||
// Merge hoisted statements into module body.
|
||||
// Ensure imports are on top, with the order preserved, then module body, then hoisted statements
|
||||
/** @type {ESTree.ImportDeclaration[]} */
|
||||
const imports = [];
|
||||
/** @type {ESTree.Program['body']} */
|
||||
let body = [];
|
||||
|
||||
for (const entry of [...module.body, ...state.hoisted]) {
|
||||
if (entry.type === 'ImportDeclaration') {
|
||||
imports.push(entry);
|
||||
} else {
|
||||
body.push(entry);
|
||||
}
|
||||
}
|
||||
|
||||
body = [...imports, ...state.module_level_snippets, ...body];
|
||||
|
||||
const component = b.function_declaration(
|
||||
b.id(analysis.name),
|
||||
should_inject_props ? [b.id('$$anchor'), b.id('$$props')] : [b.id('$$anchor')],
|
||||
component_block
|
||||
);
|
||||
|
||||
if (options.hmr) {
|
||||
const id = b.id(analysis.name);
|
||||
const HMR = b.id('$.HMR');
|
||||
|
||||
const existing = b.member(id, HMR, true);
|
||||
const incoming = b.member(b.id('module.default'), HMR, true);
|
||||
|
||||
const accept_fn_body = [
|
||||
b.stmt(b.assignment('=', b.member(incoming, 'source'), b.member(existing, 'source'))),
|
||||
b.stmt(b.call('$.set', b.member(existing, 'source'), b.member(incoming, 'original')))
|
||||
];
|
||||
|
||||
if (analysis.css.hash) {
|
||||
// remove existing `<style>` element, in case CSS changed
|
||||
accept_fn_body.unshift(b.stmt(b.call('$.cleanup_styles', b.literal(analysis.css.hash))));
|
||||
}
|
||||
|
||||
const hmr = b.block([
|
||||
b.stmt(b.assignment('=', id, b.call('$.hmr', id, b.thunk(b.member(existing, 'source'))))),
|
||||
|
||||
b.stmt(b.call('import.meta.hot.accept', b.arrow([b.id('module')], b.block(accept_fn_body))))
|
||||
]);
|
||||
|
||||
body.push(component, b.if(b.id('import.meta.hot'), hmr), b.export_default(b.id(analysis.name)));
|
||||
} else {
|
||||
body.push(b.export_default(component));
|
||||
}
|
||||
|
||||
if (dev) {
|
||||
// add `App[$.FILENAME] = 'App.svelte'` so that we can print useful messages later
|
||||
body.unshift(
|
||||
b.stmt(
|
||||
b.assignment('=', b.member(b.id(analysis.name), '$.FILENAME', true), b.literal(filename))
|
||||
)
|
||||
);
|
||||
|
||||
body.unshift(b.stmt(b.call(b.id('$.mark_module_start'))));
|
||||
body.push(b.stmt(b.call(b.id('$.mark_module_end'), b.id(analysis.name))));
|
||||
}
|
||||
|
||||
if (!analysis.runes) {
|
||||
body.unshift(b.imports([], 'svelte/internal/flags/legacy'));
|
||||
}
|
||||
|
||||
if (analysis.tracing) {
|
||||
body.unshift(b.imports([], 'svelte/internal/flags/tracing'));
|
||||
}
|
||||
|
||||
if (options.discloseVersion) {
|
||||
body.unshift(b.imports([], 'svelte/internal/disclose-version'));
|
||||
}
|
||||
|
||||
if (options.compatibility.componentApi === 4) {
|
||||
body.unshift(b.imports([['createClassComponent', '$$_createClassComponent']], 'svelte/legacy'));
|
||||
component_block.body.unshift(
|
||||
b.if(
|
||||
b.id('new.target'),
|
||||
b.return(
|
||||
b.call(
|
||||
'$$_createClassComponent',
|
||||
// When called with new, the first argument is the constructor options
|
||||
b.object([b.init('component', b.id(analysis.name)), b.spread(b.id('$$anchor'))])
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
} else if (dev) {
|
||||
component_block.body.unshift(b.stmt(b.call('$.check_target', b.id('new.target'))));
|
||||
}
|
||||
|
||||
if (state.events.size > 0) {
|
||||
body.push(
|
||||
b.stmt(b.call('$.delegate', b.array(Array.from(state.events).map((name) => b.literal(name)))))
|
||||
);
|
||||
}
|
||||
|
||||
if (analysis.custom_element) {
|
||||
const ce = analysis.custom_element;
|
||||
const ce_props = typeof ce === 'boolean' ? {} : ce.props || {};
|
||||
|
||||
/** @type {ESTree.Property[]} */
|
||||
const props_str = [];
|
||||
|
||||
for (const [name, prop_def] of Object.entries(ce_props)) {
|
||||
const binding = analysis.instance.scope.get(name);
|
||||
const key = binding?.prop_alias ?? name;
|
||||
|
||||
if (
|
||||
!prop_def.type &&
|
||||
binding?.initial?.type === 'Literal' &&
|
||||
typeof binding?.initial.value === 'boolean'
|
||||
) {
|
||||
prop_def.type = 'Boolean';
|
||||
}
|
||||
|
||||
const value = b.object(
|
||||
/** @type {ESTree.Property[]} */ (
|
||||
[
|
||||
prop_def.attribute ? b.init('attribute', b.literal(prop_def.attribute)) : undefined,
|
||||
prop_def.reflect ? b.init('reflect', b.literal(true)) : undefined,
|
||||
prop_def.type ? b.init('type', b.literal(prop_def.type)) : undefined
|
||||
].filter(Boolean)
|
||||
)
|
||||
);
|
||||
|
||||
props_str.push(b.init(key, value));
|
||||
}
|
||||
|
||||
for (const [name, binding] of properties) {
|
||||
const key = binding.prop_alias ?? name;
|
||||
if (ce_props[key]) continue;
|
||||
|
||||
props_str.push(b.init(key, b.object([])));
|
||||
}
|
||||
|
||||
const slots_str = b.array([...analysis.slot_names.keys()].map((name) => b.literal(name)));
|
||||
const accessors_str = b.array(
|
||||
analysis.exports.map(({ name, alias }) => b.literal(alias ?? name))
|
||||
);
|
||||
const use_shadow_dom = typeof ce === 'boolean' || ce.shadow !== 'none' ? true : false;
|
||||
|
||||
const create_ce = b.call(
|
||||
'$.create_custom_element',
|
||||
b.id(analysis.name),
|
||||
b.object(props_str),
|
||||
slots_str,
|
||||
accessors_str,
|
||||
b.literal(use_shadow_dom),
|
||||
/** @type {any} */ (typeof ce !== 'boolean' ? ce.extend : undefined)
|
||||
);
|
||||
|
||||
// If a tag name is provided, call `customElements.define`, otherwise leave to the user
|
||||
if (typeof ce !== 'boolean' && typeof ce.tag === 'string') {
|
||||
const define = b.stmt(b.call('customElements.define', b.literal(ce.tag), create_ce));
|
||||
|
||||
if (options.hmr) {
|
||||
body.push(
|
||||
b.if(b.binary('==', b.call('customElements.get', b.literal(ce.tag)), b.null), define)
|
||||
);
|
||||
} else {
|
||||
body.push(define);
|
||||
}
|
||||
} else {
|
||||
body.push(b.stmt(create_ce));
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'Program',
|
||||
sourceType: 'module',
|
||||
body
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Analysis} analysis
|
||||
* @param {ValidatedModuleCompileOptions} options
|
||||
* @returns {ESTree.Program}
|
||||
*/
|
||||
export function client_module(analysis, options) {
|
||||
/** @type {ClientTransformState} */
|
||||
const state = {
|
||||
analysis,
|
||||
options,
|
||||
scope: analysis.module.scope,
|
||||
scopes: analysis.module.scopes,
|
||||
public_state: new Map(),
|
||||
private_state: new Map(),
|
||||
transform: {},
|
||||
in_constructor: false
|
||||
};
|
||||
|
||||
const module = /** @type {ESTree.Program} */ (
|
||||
walk(/** @type {AST.SvelteNode} */ (analysis.module.ast), state, visitors)
|
||||
);
|
||||
|
||||
const body = [b.import_all('$', 'svelte/internal/client')];
|
||||
|
||||
if (analysis.tracing) {
|
||||
body.push(b.imports([], 'svelte/internal/flags/tracing'));
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'Program',
|
||||
sourceType: 'module',
|
||||
body: [...body, ...module.body]
|
||||
};
|
||||
}
|
||||
111
node_modules/svelte/src/compiler/phases/3-transform/client/types.d.ts
generated
vendored
Normal file
111
node_modules/svelte/src/compiler/phases/3-transform/client/types.d.ts
generated
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
import type {
|
||||
ModuleDeclaration,
|
||||
Statement,
|
||||
LabeledStatement,
|
||||
Identifier,
|
||||
PrivateIdentifier,
|
||||
Expression,
|
||||
AssignmentExpression,
|
||||
UpdateExpression,
|
||||
VariableDeclaration
|
||||
} from 'estree';
|
||||
import type { AST, Namespace, ValidatedCompileOptions } from '#compiler';
|
||||
import type { TransformState } from '../types.js';
|
||||
import type { ComponentAnalysis } from '../../types.js';
|
||||
import type { SourceLocation } from '#shared';
|
||||
|
||||
export interface ClientTransformState extends TransformState {
|
||||
readonly private_state: Map<string, StateField>;
|
||||
readonly public_state: Map<string, StateField>;
|
||||
|
||||
/**
|
||||
* `true` if the current lexical scope belongs to a class constructor. this allows
|
||||
* us to rewrite `this.foo` as `this.#foo.value`
|
||||
*/
|
||||
readonly in_constructor: boolean;
|
||||
|
||||
readonly transform: Record<
|
||||
string,
|
||||
{
|
||||
/** turn `foo` into e.g. `$.get(foo)` */
|
||||
read: (id: Identifier) => Expression;
|
||||
/** turn `foo = bar` into e.g. `$.set(foo, bar)` */
|
||||
assign?: (node: Identifier, value: Expression) => Expression;
|
||||
/** turn `foo.bar = baz` into e.g. `$.mutate(foo, $.get(foo).bar = baz);` */
|
||||
mutate?: (node: Identifier, mutation: AssignmentExpression | UpdateExpression) => Expression;
|
||||
/** turn `foo++` into e.g. `$.update(foo)` */
|
||||
update?: (node: UpdateExpression) => Expression;
|
||||
}
|
||||
>;
|
||||
}
|
||||
|
||||
export interface ComponentClientTransformState extends ClientTransformState {
|
||||
readonly analysis: ComponentAnalysis;
|
||||
readonly options: ValidatedCompileOptions;
|
||||
readonly hoisted: Array<Statement | ModuleDeclaration>;
|
||||
readonly events: Set<string>;
|
||||
readonly is_instance: boolean;
|
||||
|
||||
/** Stuff that happens before the render effect(s) */
|
||||
readonly init: Statement[];
|
||||
/** Stuff that happens inside the render effect */
|
||||
readonly update: Statement[];
|
||||
/** Stuff that happens after the render effect (control blocks, dynamic elements, bindings, actions, etc) */
|
||||
readonly after_update: Statement[];
|
||||
/** Expressions used inside the render effect */
|
||||
readonly expressions: Expression[];
|
||||
/** The HTML template string */
|
||||
readonly template: Array<string | Expression>;
|
||||
readonly locations: SourceLocation[];
|
||||
readonly metadata: {
|
||||
namespace: Namespace;
|
||||
bound_contenteditable: boolean;
|
||||
/**
|
||||
* Stuff that is set within the children of one `Fragment` visitor that is relevant
|
||||
* to said fragment. Shouldn't be destructured or otherwise spread unless inside the
|
||||
* `Fragment` visitor to keep the object reference intact (it's also nested
|
||||
* within `metadata` for this reason).
|
||||
*/
|
||||
context: {
|
||||
/** `true` if the HTML template needs to be instantiated with `importNode` */
|
||||
template_needs_import_node: boolean;
|
||||
/**
|
||||
* `true` if HTML template contains a `<script>` tag. In this case we need to invoke a special
|
||||
* template instantiation function (see `create_fragment_with_script_from_html` for more info)
|
||||
*/
|
||||
template_contains_script_tag: boolean;
|
||||
};
|
||||
};
|
||||
readonly preserve_whitespace: boolean;
|
||||
|
||||
/** The anchor node for the current context */
|
||||
readonly node: Identifier;
|
||||
|
||||
/** Imports that should be re-evaluated in legacy mode following a mutation */
|
||||
readonly legacy_reactive_imports: Statement[];
|
||||
|
||||
/** The $: calls, which will be ordered in the end */
|
||||
readonly legacy_reactive_statements: Map<LabeledStatement, Statement>;
|
||||
|
||||
/** Snippets hoisted to the instance */
|
||||
readonly instance_level_snippets: VariableDeclaration[];
|
||||
/** Snippets hoisted to the module */
|
||||
readonly module_level_snippets: VariableDeclaration[];
|
||||
}
|
||||
|
||||
export interface StateField {
|
||||
kind: 'state' | 'raw_state' | 'derived' | 'derived_by';
|
||||
id: PrivateIdentifier;
|
||||
}
|
||||
|
||||
export type Context = import('zimmerframe').Context<AST.SvelteNode, ClientTransformState>;
|
||||
export type Visitors = import('zimmerframe').Visitors<AST.SvelteNode, any>;
|
||||
|
||||
export type ComponentContext = import('zimmerframe').Context<
|
||||
AST.SvelteNode,
|
||||
ComponentClientTransformState
|
||||
>;
|
||||
export type ComponentVisitors = import('zimmerframe').Visitors<
|
||||
AST.SvelteNode,
|
||||
ComponentClientTransformState
|
||||
>;
|
||||
279
node_modules/svelte/src/compiler/phases/3-transform/client/utils.js
generated
vendored
Normal file
279
node_modules/svelte/src/compiler/phases/3-transform/client/utils.js
generated
vendored
Normal file
@@ -0,0 +1,279 @@
|
||||
/** @import { ArrowFunctionExpression, Expression, FunctionDeclaration, FunctionExpression, Identifier, Pattern, PrivateIdentifier, Statement } from 'estree' */
|
||||
/** @import { AST, Binding } from '#compiler' */
|
||||
/** @import { ClientTransformState, ComponentClientTransformState, ComponentContext } from './types.js' */
|
||||
/** @import { Analysis } from '../../types.js' */
|
||||
/** @import { Scope } from '../../scope.js' */
|
||||
import * as b from '../../../utils/builders.js';
|
||||
import { extract_identifiers, is_simple_expression } from '../../../utils/ast.js';
|
||||
import {
|
||||
PROPS_IS_LAZY_INITIAL,
|
||||
PROPS_IS_IMMUTABLE,
|
||||
PROPS_IS_RUNES,
|
||||
PROPS_IS_UPDATED,
|
||||
PROPS_IS_BINDABLE
|
||||
} from '../../../../constants.js';
|
||||
import { dev } from '../../../state.js';
|
||||
import { get_value } from './visitors/shared/declarations.js';
|
||||
|
||||
/**
|
||||
* @param {Binding} binding
|
||||
* @param {Analysis} analysis
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function is_state_source(binding, analysis) {
|
||||
return (
|
||||
(binding.kind === 'state' || binding.kind === 'raw_state') &&
|
||||
(!analysis.immutable || binding.reassigned || analysis.accessors)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Identifier} node
|
||||
* @param {ClientTransformState} state
|
||||
* @returns {Expression}
|
||||
*/
|
||||
export function build_getter(node, state) {
|
||||
if (Object.hasOwn(state.transform, node.name)) {
|
||||
const binding = state.scope.get(node.name);
|
||||
|
||||
// don't transform the declaration itself
|
||||
if (node !== binding?.node) {
|
||||
return state.transform[node.name].read(node);
|
||||
}
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Expression} value
|
||||
* @param {Expression} previous
|
||||
*/
|
||||
export function build_proxy_reassignment(value, previous) {
|
||||
return dev ? b.call('$.proxy', value, b.null, previous) : b.call('$.proxy', value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {FunctionDeclaration | FunctionExpression | ArrowFunctionExpression} node
|
||||
* @param {ComponentContext} context
|
||||
* @returns {Pattern[]}
|
||||
*/
|
||||
function get_hoisted_params(node, context) {
|
||||
const scope = context.state.scope;
|
||||
|
||||
/** @type {Identifier[]} */
|
||||
const params = [];
|
||||
|
||||
/**
|
||||
* We only want to push if it's not already present to avoid name clashing
|
||||
* @param {Identifier} id
|
||||
*/
|
||||
function push_unique(id) {
|
||||
if (!params.find((param) => param.name === id.name)) {
|
||||
params.push(id);
|
||||
}
|
||||
}
|
||||
|
||||
for (const [reference] of scope.references) {
|
||||
let binding = scope.get(reference);
|
||||
|
||||
if (binding !== null && !scope.declarations.has(reference) && binding.initial !== node) {
|
||||
if (binding.kind === 'store_sub') {
|
||||
// We need both the subscription for getting the value and the store for updating
|
||||
push_unique(b.id(binding.node.name));
|
||||
binding = /** @type {Binding} */ (scope.get(binding.node.name.slice(1)));
|
||||
}
|
||||
|
||||
let expression = context.state.transform[reference]?.read(b.id(binding.node.name));
|
||||
|
||||
if (
|
||||
// If it's a destructured derived binding, then we can extract the derived signal reference and use that.
|
||||
// TODO this code is bad, we need to kill it
|
||||
expression != null &&
|
||||
typeof expression !== 'function' &&
|
||||
expression.type === 'MemberExpression' &&
|
||||
expression.object.type === 'CallExpression' &&
|
||||
expression.object.callee.type === 'Identifier' &&
|
||||
expression.object.callee.name === '$.get' &&
|
||||
expression.object.arguments[0].type === 'Identifier'
|
||||
) {
|
||||
push_unique(b.id(expression.object.arguments[0].name));
|
||||
} else if (
|
||||
// If we are referencing a simple $$props value, then we need to reference the object property instead
|
||||
(binding.kind === 'prop' || binding.kind === 'bindable_prop') &&
|
||||
!is_prop_source(binding, context.state)
|
||||
) {
|
||||
push_unique(b.id('$$props'));
|
||||
} else if (
|
||||
// imports don't need to be hoisted
|
||||
binding.declaration_kind !== 'import'
|
||||
) {
|
||||
// create a copy to remove start/end tags which would mess up source maps
|
||||
push_unique(b.id(binding.node.name));
|
||||
// rest props are often accessed through the $$props object for optimization reasons,
|
||||
// but we can't know if the delegated event handler will use it, so we need to add both as params
|
||||
if (binding.kind === 'rest_prop' && context.state.analysis.runes) {
|
||||
push_unique(b.id('$$props'));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {FunctionDeclaration | FunctionExpression | ArrowFunctionExpression} node
|
||||
* @param {ComponentContext} context
|
||||
* @returns {Pattern[]}
|
||||
*/
|
||||
export function build_hoisted_params(node, context) {
|
||||
const hoisted_params = get_hoisted_params(node, context);
|
||||
node.metadata.hoisted_params = hoisted_params;
|
||||
|
||||
/** @type {Pattern[]} */
|
||||
const params = [];
|
||||
|
||||
if (node.params.length === 0) {
|
||||
if (hoisted_params.length > 0) {
|
||||
// For the event object
|
||||
params.push(b.id(context.state.scope.generate('_')));
|
||||
}
|
||||
} else {
|
||||
for (const param of node.params) {
|
||||
params.push(/** @type {Pattern} */ (context.visit(param)));
|
||||
}
|
||||
}
|
||||
|
||||
params.push(...hoisted_params);
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Binding} binding
|
||||
* @param {ComponentClientTransformState} state
|
||||
* @param {string} name
|
||||
* @param {Expression | null} [initial]
|
||||
* @returns
|
||||
*/
|
||||
export function get_prop_source(binding, state, name, initial) {
|
||||
/** @type {Expression[]} */
|
||||
const args = [b.id('$$props'), b.literal(name)];
|
||||
|
||||
let flags = 0;
|
||||
|
||||
if (binding.kind === 'bindable_prop') {
|
||||
flags |= PROPS_IS_BINDABLE;
|
||||
}
|
||||
|
||||
if (state.analysis.immutable) {
|
||||
flags |= PROPS_IS_IMMUTABLE;
|
||||
}
|
||||
|
||||
if (state.analysis.runes) {
|
||||
flags |= PROPS_IS_RUNES;
|
||||
}
|
||||
|
||||
if (
|
||||
state.analysis.accessors ||
|
||||
(state.analysis.immutable
|
||||
? binding.reassigned || (state.analysis.runes && binding.mutated)
|
||||
: binding.updated)
|
||||
) {
|
||||
flags |= PROPS_IS_UPDATED;
|
||||
}
|
||||
|
||||
/** @type {Expression | undefined} */
|
||||
let arg;
|
||||
|
||||
if (initial) {
|
||||
// To avoid eagerly evaluating the right-hand-side, we wrap it in a thunk if necessary
|
||||
if (is_simple_expression(initial)) {
|
||||
arg = initial;
|
||||
} else {
|
||||
if (
|
||||
initial.type === 'CallExpression' &&
|
||||
initial.callee.type === 'Identifier' &&
|
||||
initial.arguments.length === 0
|
||||
) {
|
||||
arg = initial.callee;
|
||||
} else {
|
||||
arg = b.thunk(initial);
|
||||
}
|
||||
|
||||
flags |= PROPS_IS_LAZY_INITIAL;
|
||||
}
|
||||
}
|
||||
|
||||
if (flags || arg) {
|
||||
args.push(b.literal(flags));
|
||||
if (arg) args.push(arg);
|
||||
}
|
||||
|
||||
return b.call('$.prop', ...args);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Binding} binding
|
||||
* @param {ClientTransformState} state
|
||||
* @returns
|
||||
*/
|
||||
export function is_prop_source(binding, state) {
|
||||
return (
|
||||
(binding.kind === 'prop' || binding.kind === 'bindable_prop') &&
|
||||
(!state.analysis.runes ||
|
||||
state.analysis.accessors ||
|
||||
binding.reassigned ||
|
||||
binding.initial ||
|
||||
// Until legacy mode is gone, we also need to use the prop source when only mutated is true,
|
||||
// because the parent could be a legacy component which needs coarse-grained reactivity
|
||||
binding.updated)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Expression} node
|
||||
* @param {Scope | null} scope
|
||||
*/
|
||||
export function should_proxy(node, scope) {
|
||||
if (
|
||||
!node ||
|
||||
node.type === 'Literal' ||
|
||||
node.type === 'TemplateLiteral' ||
|
||||
node.type === 'ArrowFunctionExpression' ||
|
||||
node.type === 'FunctionExpression' ||
|
||||
node.type === 'UnaryExpression' ||
|
||||
node.type === 'BinaryExpression' ||
|
||||
(node.type === 'Identifier' && node.name === 'undefined')
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (node.type === 'Identifier' && scope !== null) {
|
||||
const binding = scope.get(node.name);
|
||||
// Let's see if the reference is something that can be proxied
|
||||
if (
|
||||
binding !== null &&
|
||||
!binding.reassigned &&
|
||||
binding.initial !== null &&
|
||||
binding.initial.type !== 'FunctionDeclaration' &&
|
||||
binding.initial.type !== 'ClassDeclaration' &&
|
||||
binding.initial.type !== 'ImportDeclaration' &&
|
||||
binding.initial.type !== 'EachBlock' &&
|
||||
binding.initial.type !== 'SnippetBlock'
|
||||
) {
|
||||
return should_proxy(binding.initial, null);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Svelte legacy mode should use safe equals in most places, runes mode shouldn't
|
||||
* @param {ComponentClientTransformState} state
|
||||
* @param {Expression} arg
|
||||
*/
|
||||
export function create_derived(state, arg) {
|
||||
return b.call(state.analysis.runes ? '$.derived' : '$.derived_safe_equal', arg);
|
||||
}
|
||||
28
node_modules/svelte/src/compiler/phases/3-transform/client/visitors/AnimateDirective.js
generated
vendored
Normal file
28
node_modules/svelte/src/compiler/phases/3-transform/client/visitors/AnimateDirective.js
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
/** @import { Expression } from 'estree' */
|
||||
/** @import { AST } from '#compiler' */
|
||||
/** @import { ComponentContext } from '../types' */
|
||||
import * as b from '../../../../utils/builders.js';
|
||||
import { parse_directive_name } from './shared/utils.js';
|
||||
|
||||
/**
|
||||
* @param {AST.AnimateDirective} node
|
||||
* @param {ComponentContext} context
|
||||
*/
|
||||
export function AnimateDirective(node, context) {
|
||||
const expression =
|
||||
node.expression === null
|
||||
? b.literal(null)
|
||||
: b.thunk(/** @type {Expression} */ (context.visit(node.expression)));
|
||||
|
||||
// in after_update to ensure it always happens after bind:this
|
||||
context.state.after_update.push(
|
||||
b.stmt(
|
||||
b.call(
|
||||
'$.animation',
|
||||
context.state.node,
|
||||
b.thunk(/** @type {Expression} */ (context.visit(parse_directive_name(node.name)))),
|
||||
expression
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
11
node_modules/svelte/src/compiler/phases/3-transform/client/visitors/ArrowFunctionExpression.js
generated
vendored
Normal file
11
node_modules/svelte/src/compiler/phases/3-transform/client/visitors/ArrowFunctionExpression.js
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
/** @import { ArrowFunctionExpression } from 'estree' */
|
||||
/** @import { ComponentContext } from '../types' */
|
||||
import { visit_function } from './shared/function.js';
|
||||
|
||||
/**
|
||||
* @param {ArrowFunctionExpression} node
|
||||
* @param {ComponentContext} context
|
||||
*/
|
||||
export function ArrowFunctionExpression(node, context) {
|
||||
return visit_function(node, context);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user