@ -32,6 +32,7 @@ import {
import { Scope , get _rune } from '../scope.js' ;
import { merge } from '../visitors.js' ;
import { a11y _validators } from './a11y.js' ;
import { can _hoist _inline _class _expression } from '../3-transform/utils.js' ;
/** @param {import('#compiler').Attribute} attribute */
function validate _attribute ( attribute ) {
@ -329,6 +330,102 @@ function validate_block_not_empty(node, context) {
}
}
/ * *
* @ type { import ( 'zimmerframe' ) . Visitors < import ( '#compiler' ) . SvelteNode , import ( './types.js' ) . AnalysisState > }
* /
export const validation _runes _js = {
ImportDeclaration ( node ) {
if ( typeof node . source . value === 'string' && node . source . value . startsWith ( 'svelte/internal' ) ) {
e . import _svelte _internal _forbidden ( node ) ;
}
} ,
ExportSpecifier ( node , { state } ) {
validate _export ( node , state . scope , node . local . name ) ;
} ,
ExportNamedDeclaration ( node , { state , next } ) {
if ( node . declaration ? . type !== 'VariableDeclaration' ) return ;
// visit children, so bindings are correctly initialised
next ( ) ;
for ( const declarator of node . declaration . declarations ) {
for ( const id of extract _identifiers ( declarator . id ) ) {
validate _export ( node , state . scope , id . name ) ;
}
}
} ,
CallExpression ( node , { state , path } ) {
if ( get _rune ( node , state . scope ) === '$host' ) {
e . host _invalid _placement ( node ) ;
}
validate _call _expression ( node , state . scope , path ) ;
} ,
VariableDeclarator ( node , { state } ) {
const init = node . init ;
const rune = get _rune ( init , state . scope ) ;
if ( rune === null ) return ;
const args = /** @type {import('estree').CallExpression} */ ( init ) . arguments ;
if ( ( rune === '$derived' || rune === '$derived.by' ) && args . length !== 1 ) {
e . rune _invalid _arguments _length ( node , rune , 'exactly one argument' ) ;
} else if ( rune === '$state' && args . length > 1 ) {
e . rune _invalid _arguments _length ( node , rune , 'zero or one arguments' ) ;
} else if ( rune === '$props' ) {
e . props _invalid _placement ( node ) ;
} else if ( rune === '$bindable' ) {
e . bindable _invalid _location ( node ) ;
}
} ,
AssignmentExpression ( node , { state } ) {
validate _assignment ( node , node . left , state ) ;
} ,
UpdateExpression ( node , { state } ) {
validate _assignment ( node , node . argument , state ) ;
} ,
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
} ) ;
} ,
ClassDeclaration ( node , context ) {
// 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 ) ;
}
} ,
NewExpression ( node , context ) {
if (
node . callee . type === 'ClassExpression' &&
! can _hoist _inline _class _expression ( node , context . state . ast _type , context . state . scope )
) {
w . perf _avoid _inline _class ( node ) ;
}
}
} ;
/ * *
* @ type { import ( 'zimmerframe' ) . Visitors < import ( '#compiler' ) . SvelteNode , import ( './types.js' ) . AnalysisState > }
* /
@ -807,7 +904,8 @@ export const validation_legacy = merge(validation, a11y_validators, {
} ,
UpdateExpression ( node , { state } ) {
validate _assignment ( node , node . argument , state ) ;
}
} ,
NewExpression : validation _runes _js . NewExpression
} ) ;
/ * *
@ -933,99 +1031,6 @@ function ensure_no_module_import_conflict(node, state) {
}
}
/ * *
* @ type { import ( 'zimmerframe' ) . Visitors < import ( '#compiler' ) . SvelteNode , import ( './types.js' ) . AnalysisState > }
* /
export const validation _runes _js = {
ImportDeclaration ( node ) {
if ( typeof node . source . value === 'string' && node . source . value . startsWith ( 'svelte/internal' ) ) {
e . import _svelte _internal _forbidden ( node ) ;
}
} ,
ExportSpecifier ( node , { state } ) {
validate _export ( node , state . scope , node . local . name ) ;
} ,
ExportNamedDeclaration ( node , { state , next } ) {
if ( node . declaration ? . type !== 'VariableDeclaration' ) return ;
// visit children, so bindings are correctly initialised
next ( ) ;
for ( const declarator of node . declaration . declarations ) {
for ( const id of extract _identifiers ( declarator . id ) ) {
validate _export ( node , state . scope , id . name ) ;
}
}
} ,
CallExpression ( node , { state , path } ) {
if ( get _rune ( node , state . scope ) === '$host' ) {
e . host _invalid _placement ( node ) ;
}
validate _call _expression ( node , state . scope , path ) ;
} ,
VariableDeclarator ( node , { state } ) {
const init = node . init ;
const rune = get _rune ( init , state . scope ) ;
if ( rune === null ) return ;
const args = /** @type {import('estree').CallExpression} */ ( init ) . arguments ;
if ( ( rune === '$derived' || rune === '$derived.by' ) && args . length !== 1 ) {
e . rune _invalid _arguments _length ( node , rune , 'exactly one argument' ) ;
} else if ( rune === '$state' && args . length > 1 ) {
e . rune _invalid _arguments _length ( node , rune , 'zero or one arguments' ) ;
} else if ( rune === '$props' ) {
e . props _invalid _placement ( node ) ;
} else if ( rune === '$bindable' ) {
e . bindable _invalid _location ( node ) ;
}
} ,
AssignmentExpression ( node , { state } ) {
validate _assignment ( node , node . left , state ) ;
} ,
UpdateExpression ( node , { state } ) {
validate _assignment ( node , node . argument , state ) ;
} ,
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
} ) ;
} ,
ClassDeclaration ( node , context ) {
// 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 ) ;
}
} ,
NewExpression ( node , context ) {
if ( node . callee . type === 'ClassExpression' && context . state . scope . function _depth > 0 ) {
w . perf _avoid _inline _class ( node ) ;
}
}
} ;
/ * *
* @ param { import ( '../../errors.js' ) . NodeLike } node
* @ param { import ( 'estree' ) . Pattern | import ( 'estree' ) . Expression } argument