Comparing version 5.4.0 to 5.5.0
@@ -5,3 +5,3 @@ { | ||
"license": "MIT", | ||
"version": "5.4.0", | ||
"version": "5.5.0", | ||
"type": "module", | ||
@@ -8,0 +8,0 @@ "types": "./types/index.d.ts", |
@@ -172,2 +172,12 @@ /* This file is generated by scripts/process-messages/index.js. Do not edit! */ | ||
/** | ||
* `%name%` is not defined | ||
* @param {null | number | NodeLike} node | ||
* @param {string} name | ||
* @returns {never} | ||
*/ | ||
export function export_undefined(node, name) { | ||
e(node, "export_undefined", `\`${name}\` is not defined`); | ||
} | ||
/** | ||
* `%name%` is an illegal variable name. To reference a global variable called `%name%`, use `globalThis.%name%` | ||
@@ -400,2 +410,11 @@ * @param {null | number | NodeLike} node | ||
/** | ||
* An exported snippet can only reference things declared in a `<script module>`, or other exportable snippets | ||
* @param {null | number | NodeLike} node | ||
* @returns {never} | ||
*/ | ||
export function snippet_invalid_export(node) { | ||
e(node, "snippet_invalid_export", "An exported snippet can only reference things declared in a `<script module>`, or other exportable snippets"); | ||
} | ||
/** | ||
* Cannot reassign or bind to snippet parameter | ||
@@ -402,0 +421,0 @@ * @param {null | number | NodeLike} node |
@@ -13,14 +13,40 @@ /** @import { Comment, Program } from 'estree' */ | ||
* @param {boolean} typescript | ||
* @param {boolean} [is_script] | ||
*/ | ||
export function parse(source, typescript) { | ||
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; | ||
const ast = parser.parse(source, { | ||
onComment, | ||
sourceType: 'module', | ||
ecmaVersion: 13, | ||
locations: true | ||
}); | ||
// 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); | ||
@@ -27,0 +53,0 @@ add_comments(ast); |
@@ -37,3 +37,3 @@ /** @import { Program } from 'estree' */ | ||
try { | ||
ast = acorn.parse(source, parser.ts); | ||
ast = acorn.parse(source, parser.ts, true); | ||
} catch (err) { | ||
@@ -40,0 +40,0 @@ parser.acorn_error(err); |
@@ -107,2 +107,5 @@ /** @import { Context, Visitors } from 'zimmerframe' */ | ||
}, | ||
TSInstantiationExpression(node, context) { | ||
return context.visit(node.expression); | ||
}, | ||
FunctionExpression: remove_this_param, | ||
@@ -109,0 +112,0 @@ FunctionDeclaration: remove_this_param, |
@@ -328,2 +328,3 @@ /** @import { ArrowFunctionExpression, Expression, Identifier, Pattern } from 'estree' */ | ||
metadata: { | ||
can_hoist: false, | ||
sites: new Set() | ||
@@ -330,0 +331,0 @@ } |
@@ -432,3 +432,2 @@ /** @import { Expression, Node, Program } from 'estree' */ | ||
slot_names: new Map(), | ||
top_level_snippets: [], | ||
css: { | ||
@@ -447,2 +446,3 @@ ast: root.css, | ||
source, | ||
undefined_exports: new Map(), | ||
snippet_renderers: new Map(), | ||
@@ -702,2 +702,13 @@ snippets: new Set() | ||
for (const node of analysis.module.ast.body) { | ||
if (node.type === 'ExportNamedDeclaration' && node.specifiers !== 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) { | ||
@@ -704,0 +715,0 @@ e.mixed_event_handler_syntaxes( |
@@ -1,2 +0,3 @@ | ||
/** @import { AST } from '#compiler' */ | ||
/** @import { AST, Binding, SvelteNode } from '#compiler' */ | ||
/** @import { Scope } from '../../scope' */ | ||
/** @import { Context } from '../types' */ | ||
@@ -27,2 +28,21 @@ import { validate_block_not_empty, validate_opening_tag } from './shared/utils.js'; | ||
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; | ||
@@ -62,1 +82,33 @@ const parent = path.at(-2); | ||
} | ||
/** | ||
* @param {Map<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); | ||
if (can_hoist_snippet(binding.scope, scopes, visited)) { | ||
continue; | ||
} | ||
} | ||
return false; | ||
} | ||
return true; | ||
} |
@@ -168,2 +168,4 @@ /** @import * as ESTree from 'estree' */ | ||
in_constructor: false, | ||
instance_level_snippets: [], | ||
module_level_snippets: [], | ||
@@ -374,3 +376,3 @@ // these are set inside the `Fragment` visitor, and cannot be used until then | ||
...group_binding_declarations, | ||
...analysis.top_level_snippets, | ||
...state.instance_level_snippets, | ||
.../** @type {ESTree.Statement[]} */ (instance.body), | ||
@@ -490,3 +492,3 @@ analysis.runes || !analysis.needs_context | ||
body = [...imports, ...body]; | ||
body = [...imports, ...state.module_level_snippets, ...body]; | ||
@@ -493,0 +495,0 @@ const component = b.function_declaration( |
@@ -9,3 +9,4 @@ import type { | ||
AssignmentExpression, | ||
UpdateExpression | ||
UpdateExpression, | ||
VariableDeclaration | ||
} from 'estree'; | ||
@@ -89,2 +90,7 @@ import type { Namespace, SvelteNode, ValidatedCompileOptions } from '#compiler'; | ||
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[]; | ||
} | ||
@@ -91,0 +97,0 @@ |
@@ -86,3 +86,7 @@ /** @import { BlockStatement, Expression, Identifier, Pattern, Statement } from 'estree' */ | ||
if (context.path.length === 1 && context.path[0].type === 'Fragment') { | ||
context.state.analysis.top_level_snippets.push(declaration); | ||
if (node.metadata.can_hoist) { | ||
context.state.module_level_snippets.push(declaration); | ||
} else { | ||
context.state.instance_level_snippets.push(declaration); | ||
} | ||
} else { | ||
@@ -89,0 +93,0 @@ context.state.init.push(declaration); |
@@ -20,4 +20,7 @@ /** @import { BlockStatement } from 'estree' */ | ||
// TODO hoist where possible | ||
context.state.init.push(fn); | ||
if (node.metadata.can_hoist) { | ||
context.state.hoisted.push(fn); | ||
} else { | ||
context.state.init.push(fn); | ||
} | ||
} |
import type { AST, Binding, Css, SvelteNode } from '#compiler'; | ||
import type { Identifier, LabeledStatement, Program, VariableDeclaration } from 'estree'; | ||
import type { Identifier, LabeledStatement, Node, Program } from 'estree'; | ||
import type { Scope, ScopeRoot } from './scope.js'; | ||
@@ -65,3 +65,2 @@ | ||
reactive_statements: Map<LabeledStatement, ReactiveStatement>; | ||
top_level_snippets: VariableDeclaration[]; | ||
/** Identifiers that make up the `bind:group` expression -> internal group binding name */ | ||
@@ -76,2 +75,3 @@ binding_groups: Map<[key: string, bindings: Array<Binding | null>], Identifier>; | ||
source: string; | ||
undefined_exports: Map<string, Node>; | ||
/** | ||
@@ -78,0 +78,0 @@ * Every render tag/component, and whether it could be definitively resolved or not |
@@ -282,3 +282,4 @@ import type { | ||
| 'legacy_reactive' | ||
| 'template'; | ||
| 'template' | ||
| 'snippet'; | ||
declaration_kind: DeclarationKind; | ||
@@ -285,0 +286,0 @@ /** |
@@ -464,2 +464,3 @@ import type { Binding, Css, ExpressionMetadata } from '#compiler'; | ||
metadata: { | ||
can_hoist: boolean; | ||
/** The set of components/render tags that could render this snippet, | ||
@@ -466,0 +467,0 @@ * used for CSS pruning */ |
@@ -41,3 +41,3 @@ // This should contain all the public interfaces (not all of them are actually importable, check current Svelte for which ones are). | ||
* are completely different under the hood. For typing, use `Component` instead. | ||
* To instantiate components, use `mount` instead`. | ||
* To instantiate components, use `mount` instead. | ||
* See [migration guide](https://svelte.dev/docs/svelte/v5-migration-guide#Components-are-no-longer-classes) for more info. | ||
@@ -44,0 +44,0 @@ */ |
@@ -9,3 +9,3 @@ // generated during release, do not modify | ||
*/ | ||
export const VERSION = '5.4.0'; | ||
export const VERSION = '5.5.0'; | ||
export const PUBLIC_VERSION = '5'; |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
2350292
51885