mirror of https://github.com/sveltejs/svelte
commit
098a5539f3
@ -0,0 +1,162 @@
|
|||||||
|
// This script generates the TypeScript definitions
|
||||||
|
|
||||||
|
import { execSync } from 'child_process';
|
||||||
|
import { readFileSync, writeFileSync, readdirSync, existsSync, copyFileSync, statSync } from 'fs';
|
||||||
|
|
||||||
|
execSync('tsc -p src/compiler --emitDeclarationOnly && tsc -p src/runtime --emitDeclarationOnly', { stdio: 'inherit' });
|
||||||
|
|
||||||
|
function modify(path, modifyFn) {
|
||||||
|
const content = readFileSync(path, 'utf8');
|
||||||
|
writeFileSync(path, modifyFn(content));
|
||||||
|
}
|
||||||
|
|
||||||
|
function adjust(input) {
|
||||||
|
// Remove typedef jsdoc (duplicated in the type definition)
|
||||||
|
input = input.replace(/\/\*\*\n(\r)? \* @typedef .+?\*\//gs, '');
|
||||||
|
input = input.replace(/\/\*\* @typedef .+?\*\//gs, '');
|
||||||
|
|
||||||
|
// Extract the import paths and types
|
||||||
|
const import_regex = /import\(("|')(.+?)("|')\)\.(\w+)/g;
|
||||||
|
let import_match;
|
||||||
|
const import_map = new Map();
|
||||||
|
|
||||||
|
while ((import_match = import_regex.exec(input)) !== null) {
|
||||||
|
const imports = import_map.get(import_match[2]) || new Map();
|
||||||
|
let name = import_match[4];
|
||||||
|
if ([...imports.keys()].includes(name)) continue;
|
||||||
|
|
||||||
|
let i = 1;
|
||||||
|
if (name === 'default') {
|
||||||
|
name = import_match[2].split('/').pop().split('.').shift().replace(/[^a-z0-9]/gi, '_');
|
||||||
|
}
|
||||||
|
while ([...import_map].some(([path, names]) => path !== import_match[2] && names.has(name))) {
|
||||||
|
name = `${name}${i++}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
imports.set(import_match[4], name);
|
||||||
|
import_map.set(import_match[2], imports);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace inline imports with their type names
|
||||||
|
const transformed = input.replace(import_regex, (_match, _quote, path, _quote2, name) => {
|
||||||
|
return import_map.get(path).get(name);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remove/adjust @template, @param and @returns lines
|
||||||
|
// TODO rethink if we really need to do this for @param and @returns, doesn't show up in hover so unnecessary
|
||||||
|
const lines = transformed.split("\n");
|
||||||
|
|
||||||
|
let filtered_lines = [];
|
||||||
|
let removing = null;
|
||||||
|
let openCount = 1;
|
||||||
|
let closedCount = 0;
|
||||||
|
|
||||||
|
for (let line of lines) {
|
||||||
|
let start_removing = false;
|
||||||
|
if (line.trim().startsWith("* @template")) {
|
||||||
|
removing = "template";
|
||||||
|
start_removing = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line.trim().startsWith("* @param {")) {
|
||||||
|
openCount = 1;
|
||||||
|
closedCount = 0;
|
||||||
|
removing = "param";
|
||||||
|
start_removing = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line.trim().startsWith("* @returns {")) {
|
||||||
|
openCount = 1;
|
||||||
|
closedCount = 0;
|
||||||
|
removing = "returns";
|
||||||
|
start_removing = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (removing === "returns" || removing === "param") {
|
||||||
|
let i = start_removing ? line.indexOf('{') + 1 : 0;
|
||||||
|
for (; i < line.length; i++) {
|
||||||
|
if (line[i] === "{") openCount++;
|
||||||
|
if (line[i] === "}") closedCount++;
|
||||||
|
if (openCount === closedCount) break;
|
||||||
|
}
|
||||||
|
if (openCount === closedCount) {
|
||||||
|
line = start_removing ? (line.slice(0, line.indexOf('{')) + line.slice(i + 1)) : (` * @${removing} ` + line.slice(i + 1));
|
||||||
|
removing = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (removing && !start_removing && (line.trim().startsWith("* @") || line.trim().startsWith("*/"))) {
|
||||||
|
removing = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!removing) {
|
||||||
|
filtered_lines.push(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace generic type names with their plain versions
|
||||||
|
const renamed_generics = filtered_lines.map(line => {
|
||||||
|
return line.replace(/(\W|\s)([A-Z][\w\d$]*)_\d+(\W|\s)/g, "$1$2$3");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Generate the import statement for the types used
|
||||||
|
const import_statements = Array.from(import_map.entries())
|
||||||
|
.map(([path, names]) => {
|
||||||
|
const default_name = names.get('default');
|
||||||
|
names.delete('default');
|
||||||
|
const default_import = default_name ? (default_name + (names.size ? ', ' : ' ')) : '';
|
||||||
|
const named_imports = names.size ? `{ ${[...names.values()].join(', ')} } ` : '';
|
||||||
|
return `import ${default_import}${named_imports}from '${path}';`
|
||||||
|
})
|
||||||
|
.join("\n");
|
||||||
|
|
||||||
|
return [import_statements, ...renamed_generics].join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
let did_replace = false;
|
||||||
|
|
||||||
|
function walk(dir) {
|
||||||
|
const files = readdirSync(dir);
|
||||||
|
const _dir = dir.slice('types/'.length)
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
const path = `${dir}/${file}`;
|
||||||
|
if (file.endsWith('.d.ts')) {
|
||||||
|
modify(path, content => {
|
||||||
|
content = adjust(content);
|
||||||
|
|
||||||
|
if (file === 'index.d.ts' && existsSync(`src/${_dir}/public.d.ts`)) {
|
||||||
|
copyFileSync(`src/${_dir}/public.d.ts`, `${dir}/public.d.ts`);
|
||||||
|
content = "export * from './public.js';\n" + content;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file === 'Component.d.ts' && dir.includes('runtime')) {
|
||||||
|
if (!content.includes('$set(props: Partial<Props>): void;\n}')) {
|
||||||
|
throw new Error('Component.js was modified in a way that automatic patching of d.ts file no longer works. Please adjust it');
|
||||||
|
} else {
|
||||||
|
content = content.replace('$set(props: Partial<Props>): void;\n}', '$set(props: Partial<Props>): void;\n [accessor:string]: any;\n}');
|
||||||
|
did_replace = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return content;
|
||||||
|
});
|
||||||
|
} else if (statSync(path).isDirectory()) {
|
||||||
|
if (existsSync(`src/${_dir}/${file}/private.d.ts`)) {
|
||||||
|
copyFileSync(`src/${_dir}/${file}/private.d.ts`, `${path}/private.d.ts`);
|
||||||
|
}
|
||||||
|
if (existsSync(`src/${_dir}/${file}/interfaces.d.ts`)) {
|
||||||
|
copyFileSync(`src/${_dir}/${file}/interfaces.d.ts`, `${path}/interfaces.d.ts`);
|
||||||
|
}
|
||||||
|
walk(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
walk('types');
|
||||||
|
|
||||||
|
if (!did_replace) {
|
||||||
|
throw new Error('Component.js file in runtime does no longer exist so that automatic patching of the d.ts file no longer works. Please adjust it');
|
||||||
|
}
|
||||||
|
|
||||||
|
copyFileSync(`src/runtime/ambient.d.ts`, `types/runtime/ambient.d.ts`);
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,297 @@
|
|||||||
|
import list from '../utils/list.js';
|
||||||
|
import { b, x } from 'code-red';
|
||||||
|
const wrappers = { esm, cjs };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {any} program
|
||||||
|
* @param {import('../interfaces.js').ModuleFormat} format
|
||||||
|
* @param {import('estree').Identifier} name
|
||||||
|
* @param {string} banner
|
||||||
|
* @param {any} sveltePath
|
||||||
|
* @param {Array<{ name: string; alias: import('estree').Identifier }>} helpers
|
||||||
|
* @param {Array<{ name: string; alias: import('estree').Identifier }>} globals
|
||||||
|
* @param {import('estree').ImportDeclaration[]} imports
|
||||||
|
* @param {Export[]} module_exports
|
||||||
|
* @param {import('estree').ExportNamedDeclaration[]} exports_from
|
||||||
|
*/
|
||||||
|
export default function create_module(
|
||||||
|
program,
|
||||||
|
format,
|
||||||
|
name,
|
||||||
|
banner,
|
||||||
|
sveltePath = 'svelte',
|
||||||
|
helpers,
|
||||||
|
globals,
|
||||||
|
imports,
|
||||||
|
module_exports,
|
||||||
|
exports_from
|
||||||
|
) {
|
||||||
|
const internal_path = `${sveltePath}/internal`;
|
||||||
|
helpers.sort(
|
||||||
|
/**
|
||||||
|
* @param {any} a
|
||||||
|
* @param {any} b
|
||||||
|
*/ (a, b) => (a.name < b.name ? -1 : 1)
|
||||||
|
);
|
||||||
|
globals.sort(
|
||||||
|
/**
|
||||||
|
* @param {any} a
|
||||||
|
* @param {any} b
|
||||||
|
*/ (a, b) => (a.name < b.name ? -1 : 1)
|
||||||
|
);
|
||||||
|
const formatter = wrappers[format];
|
||||||
|
if (!formatter) {
|
||||||
|
throw new Error(`options.format is invalid (must be ${list(Object.keys(wrappers))})`);
|
||||||
|
}
|
||||||
|
return formatter(
|
||||||
|
program,
|
||||||
|
name,
|
||||||
|
banner,
|
||||||
|
sveltePath,
|
||||||
|
internal_path,
|
||||||
|
helpers,
|
||||||
|
globals,
|
||||||
|
imports,
|
||||||
|
module_exports,
|
||||||
|
exports_from
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {any} source
|
||||||
|
* @param {any} sveltePath
|
||||||
|
*/
|
||||||
|
function edit_source(source, sveltePath) {
|
||||||
|
return source === 'svelte' || source.startsWith('svelte/')
|
||||||
|
? source.replace('svelte', sveltePath)
|
||||||
|
: source;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Array<{ name: string; alias: import('estree').Identifier }>} globals
|
||||||
|
* @param {Array<{ name: string; alias: import('estree').Identifier }>} helpers
|
||||||
|
*/
|
||||||
|
function get_internal_globals(globals, helpers) {
|
||||||
|
return (
|
||||||
|
globals.length > 0 && {
|
||||||
|
type: 'VariableDeclaration',
|
||||||
|
kind: 'const',
|
||||||
|
declarations: [
|
||||||
|
{
|
||||||
|
type: 'VariableDeclarator',
|
||||||
|
id: {
|
||||||
|
type: 'ObjectPattern',
|
||||||
|
properties: globals.map(
|
||||||
|
/** @param {any} g */ (g) => ({
|
||||||
|
type: 'Property',
|
||||||
|
method: false,
|
||||||
|
shorthand: false,
|
||||||
|
computed: false,
|
||||||
|
key: { type: 'Identifier', name: g.name },
|
||||||
|
value: g.alias,
|
||||||
|
kind: 'init'
|
||||||
|
})
|
||||||
|
)
|
||||||
|
},
|
||||||
|
init: helpers.find(/** @param {any}params_0 */ ({ name }) => name === 'globals').alias
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {any} program
|
||||||
|
* @param {import('estree').Identifier} name
|
||||||
|
* @param {string} banner
|
||||||
|
* @param {string} sveltePath
|
||||||
|
* @param {string} internal_path
|
||||||
|
* @param {Array<{ name: string; alias: import('estree').Identifier }>} helpers
|
||||||
|
* @param {Array<{ name: string; alias: import('estree').Identifier }>} globals
|
||||||
|
* @param {import('estree').ImportDeclaration[]} imports
|
||||||
|
* @param {Export[]} module_exports
|
||||||
|
* @param {import('estree').ExportNamedDeclaration[]} exports_from
|
||||||
|
*/
|
||||||
|
function esm(
|
||||||
|
program,
|
||||||
|
name,
|
||||||
|
banner,
|
||||||
|
sveltePath,
|
||||||
|
internal_path,
|
||||||
|
helpers,
|
||||||
|
globals,
|
||||||
|
imports,
|
||||||
|
module_exports,
|
||||||
|
exports_from
|
||||||
|
) {
|
||||||
|
const import_declaration = {
|
||||||
|
type: 'ImportDeclaration',
|
||||||
|
specifiers: helpers.map(
|
||||||
|
/** @param {any} h */ (h) => ({
|
||||||
|
type: 'ImportSpecifier',
|
||||||
|
local: h.alias,
|
||||||
|
imported: { type: 'Identifier', name: h.name }
|
||||||
|
})
|
||||||
|
),
|
||||||
|
source: { type: 'Literal', value: internal_path }
|
||||||
|
};
|
||||||
|
const internal_globals = get_internal_globals(globals, helpers);
|
||||||
|
// edit user imports
|
||||||
|
|
||||||
|
/** @param {any} node */
|
||||||
|
function rewrite_import(node) {
|
||||||
|
const value = edit_source(node.source.value, sveltePath);
|
||||||
|
if (node.source.value !== value) {
|
||||||
|
node.source.value = value;
|
||||||
|
node.source.raw = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
imports.forEach(rewrite_import);
|
||||||
|
exports_from.forEach(rewrite_import);
|
||||||
|
const exports = module_exports.length > 0 && {
|
||||||
|
type: 'ExportNamedDeclaration',
|
||||||
|
specifiers: module_exports.map(
|
||||||
|
/** @param {any} x */ (x) => ({
|
||||||
|
type: 'Specifier',
|
||||||
|
local: { type: 'Identifier', name: x.name },
|
||||||
|
exported: { type: 'Identifier', name: x.as }
|
||||||
|
})
|
||||||
|
)
|
||||||
|
};
|
||||||
|
program.body = b`
|
||||||
|
/* ${banner} */
|
||||||
|
|
||||||
|
${import_declaration}
|
||||||
|
${internal_globals}
|
||||||
|
${imports}
|
||||||
|
${exports_from}
|
||||||
|
|
||||||
|
${program.body}
|
||||||
|
|
||||||
|
export default ${name};
|
||||||
|
${exports}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {any} program
|
||||||
|
* @param {import('estree').Identifier} name
|
||||||
|
* @param {string} banner
|
||||||
|
* @param {string} sveltePath
|
||||||
|
* @param {string} internal_path
|
||||||
|
* @param {Array<{ name: string; alias: import('estree').Identifier }>} helpers
|
||||||
|
* @param {Array<{ name: string; alias: import('estree').Identifier }>} globals
|
||||||
|
* @param {import('estree').ImportDeclaration[]} imports
|
||||||
|
* @param {Export[]} module_exports
|
||||||
|
* @param {import('estree').ExportNamedDeclaration[]} exports_from
|
||||||
|
*/
|
||||||
|
function cjs(
|
||||||
|
program,
|
||||||
|
name,
|
||||||
|
banner,
|
||||||
|
sveltePath,
|
||||||
|
internal_path,
|
||||||
|
helpers,
|
||||||
|
globals,
|
||||||
|
imports,
|
||||||
|
module_exports,
|
||||||
|
exports_from
|
||||||
|
) {
|
||||||
|
const internal_requires = {
|
||||||
|
type: 'VariableDeclaration',
|
||||||
|
kind: 'const',
|
||||||
|
declarations: [
|
||||||
|
{
|
||||||
|
type: 'VariableDeclarator',
|
||||||
|
id: {
|
||||||
|
type: 'ObjectPattern',
|
||||||
|
properties: helpers.map(
|
||||||
|
/** @param {any} h */ (h) => ({
|
||||||
|
type: 'Property',
|
||||||
|
method: false,
|
||||||
|
shorthand: false,
|
||||||
|
computed: false,
|
||||||
|
key: { type: 'Identifier', name: h.name },
|
||||||
|
value: h.alias,
|
||||||
|
kind: 'init'
|
||||||
|
})
|
||||||
|
)
|
||||||
|
},
|
||||||
|
init: x`require("${internal_path}")`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
const internal_globals = get_internal_globals(globals, helpers);
|
||||||
|
const user_requires = imports.map(
|
||||||
|
/** @param {any} node */ (node) => {
|
||||||
|
const init = x`require("${edit_source(node.source.value, sveltePath)}")`;
|
||||||
|
if (node.specifiers.length === 0) {
|
||||||
|
return b`${init};`;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
type: 'VariableDeclaration',
|
||||||
|
kind: 'const',
|
||||||
|
declarations: [
|
||||||
|
{
|
||||||
|
type: 'VariableDeclarator',
|
||||||
|
id:
|
||||||
|
node.specifiers[0].type === 'ImportNamespaceSpecifier'
|
||||||
|
? { type: 'Identifier', name: node.specifiers[0].local.name }
|
||||||
|
: {
|
||||||
|
type: 'ObjectPattern',
|
||||||
|
properties: node.specifiers.map(
|
||||||
|
/** @param {any} s */ (s) => ({
|
||||||
|
type: 'Property',
|
||||||
|
method: false,
|
||||||
|
shorthand: false,
|
||||||
|
computed: false,
|
||||||
|
key:
|
||||||
|
s.type === 'ImportSpecifier'
|
||||||
|
? s.imported
|
||||||
|
: { type: 'Identifier', name: 'default' },
|
||||||
|
value: s.local,
|
||||||
|
kind: 'init'
|
||||||
|
})
|
||||||
|
)
|
||||||
|
},
|
||||||
|
init
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const exports = module_exports.map(
|
||||||
|
/** @param {any} x */
|
||||||
|
(x) =>
|
||||||
|
b`exports.${{ type: 'Identifier', name: x.as }} = ${{ type: 'Identifier', name: x.name }};`
|
||||||
|
);
|
||||||
|
const user_exports_from = exports_from.map(
|
||||||
|
/** @param {any} node */ (node) => {
|
||||||
|
const init = x`require("${edit_source(node.source.value, sveltePath)}")`;
|
||||||
|
return node.specifiers.map(
|
||||||
|
/** @param {any} specifier */ (specifier) => {
|
||||||
|
return b`exports.${specifier.exported} = ${init}.${specifier.local};`;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
program.body = b`
|
||||||
|
/* ${banner} */
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
${internal_requires}
|
||||||
|
${internal_globals}
|
||||||
|
${user_requires}
|
||||||
|
${user_exports_from}
|
||||||
|
|
||||||
|
${program.body}
|
||||||
|
|
||||||
|
exports.default = ${name};
|
||||||
|
${exports}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @typedef {Object} Export
|
||||||
|
* @property {string} name
|
||||||
|
* @property {string} as
|
||||||
|
*/
|
@ -1,243 +0,0 @@
|
|||||||
import list from '../utils/list';
|
|
||||||
import { ModuleFormat } from '../interfaces';
|
|
||||||
import { b, x } from 'code-red';
|
|
||||||
import { Identifier, ImportDeclaration, ExportNamedDeclaration } from 'estree';
|
|
||||||
|
|
||||||
const wrappers = { esm, cjs };
|
|
||||||
|
|
||||||
interface Export {
|
|
||||||
name: string;
|
|
||||||
as: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function create_module(
|
|
||||||
program: any,
|
|
||||||
format: ModuleFormat,
|
|
||||||
name: Identifier,
|
|
||||||
banner: string,
|
|
||||||
sveltePath = 'svelte',
|
|
||||||
helpers: Array<{ name: string; alias: Identifier }>,
|
|
||||||
globals: Array<{ name: string; alias: Identifier }>,
|
|
||||||
imports: ImportDeclaration[],
|
|
||||||
module_exports: Export[],
|
|
||||||
exports_from: ExportNamedDeclaration[]
|
|
||||||
) {
|
|
||||||
const internal_path = `${sveltePath}/internal`;
|
|
||||||
|
|
||||||
helpers.sort((a, b) => (a.name < b.name ? -1 : 1));
|
|
||||||
globals.sort((a, b) => (a.name < b.name ? -1 : 1));
|
|
||||||
|
|
||||||
const formatter = wrappers[format];
|
|
||||||
|
|
||||||
if (!formatter) {
|
|
||||||
throw new Error(`options.format is invalid (must be ${list(Object.keys(wrappers))})`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return formatter(
|
|
||||||
program,
|
|
||||||
name,
|
|
||||||
banner,
|
|
||||||
sveltePath,
|
|
||||||
internal_path,
|
|
||||||
helpers,
|
|
||||||
globals,
|
|
||||||
imports,
|
|
||||||
module_exports,
|
|
||||||
exports_from
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function edit_source(source, sveltePath) {
|
|
||||||
return source === 'svelte' || source.startsWith('svelte/')
|
|
||||||
? source.replace('svelte', sveltePath)
|
|
||||||
: source;
|
|
||||||
}
|
|
||||||
|
|
||||||
function get_internal_globals(
|
|
||||||
globals: Array<{ name: string; alias: Identifier }>,
|
|
||||||
helpers: Array<{ name: string; alias: Identifier }>
|
|
||||||
) {
|
|
||||||
return (
|
|
||||||
globals.length > 0 && {
|
|
||||||
type: 'VariableDeclaration',
|
|
||||||
kind: 'const',
|
|
||||||
declarations: [
|
|
||||||
{
|
|
||||||
type: 'VariableDeclarator',
|
|
||||||
id: {
|
|
||||||
type: 'ObjectPattern',
|
|
||||||
properties: globals.map((g) => ({
|
|
||||||
type: 'Property',
|
|
||||||
method: false,
|
|
||||||
shorthand: false,
|
|
||||||
computed: false,
|
|
||||||
key: { type: 'Identifier', name: g.name },
|
|
||||||
value: g.alias,
|
|
||||||
kind: 'init'
|
|
||||||
}))
|
|
||||||
},
|
|
||||||
init: helpers.find(({ name }) => name === 'globals').alias
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function esm(
|
|
||||||
program: any,
|
|
||||||
name: Identifier,
|
|
||||||
banner: string,
|
|
||||||
sveltePath: string,
|
|
||||||
internal_path: string,
|
|
||||||
helpers: Array<{ name: string; alias: Identifier }>,
|
|
||||||
globals: Array<{ name: string; alias: Identifier }>,
|
|
||||||
imports: ImportDeclaration[],
|
|
||||||
module_exports: Export[],
|
|
||||||
exports_from: ExportNamedDeclaration[]
|
|
||||||
) {
|
|
||||||
const import_declaration = {
|
|
||||||
type: 'ImportDeclaration',
|
|
||||||
specifiers: helpers.map((h) => ({
|
|
||||||
type: 'ImportSpecifier',
|
|
||||||
local: h.alias,
|
|
||||||
imported: { type: 'Identifier', name: h.name }
|
|
||||||
})),
|
|
||||||
source: { type: 'Literal', value: internal_path }
|
|
||||||
};
|
|
||||||
|
|
||||||
const internal_globals = get_internal_globals(globals, helpers);
|
|
||||||
|
|
||||||
// edit user imports
|
|
||||||
function rewrite_import(node) {
|
|
||||||
const value = edit_source(node.source.value, sveltePath);
|
|
||||||
if (node.source.value !== value) {
|
|
||||||
node.source.value = value;
|
|
||||||
node.source.raw = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
imports.forEach(rewrite_import);
|
|
||||||
exports_from.forEach(rewrite_import);
|
|
||||||
|
|
||||||
const exports = module_exports.length > 0 && {
|
|
||||||
type: 'ExportNamedDeclaration',
|
|
||||||
specifiers: module_exports.map((x) => ({
|
|
||||||
type: 'Specifier',
|
|
||||||
local: { type: 'Identifier', name: x.name },
|
|
||||||
exported: { type: 'Identifier', name: x.as }
|
|
||||||
}))
|
|
||||||
};
|
|
||||||
|
|
||||||
program.body = b`
|
|
||||||
/* ${banner} */
|
|
||||||
|
|
||||||
${import_declaration}
|
|
||||||
${internal_globals}
|
|
||||||
${imports}
|
|
||||||
${exports_from}
|
|
||||||
|
|
||||||
${program.body}
|
|
||||||
|
|
||||||
export default ${name};
|
|
||||||
${exports}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function cjs(
|
|
||||||
program: any,
|
|
||||||
name: Identifier,
|
|
||||||
banner: string,
|
|
||||||
sveltePath: string,
|
|
||||||
internal_path: string,
|
|
||||||
helpers: Array<{ name: string; alias: Identifier }>,
|
|
||||||
globals: Array<{ name: string; alias: Identifier }>,
|
|
||||||
imports: ImportDeclaration[],
|
|
||||||
module_exports: Export[],
|
|
||||||
exports_from: ExportNamedDeclaration[]
|
|
||||||
) {
|
|
||||||
const internal_requires = {
|
|
||||||
type: 'VariableDeclaration',
|
|
||||||
kind: 'const',
|
|
||||||
declarations: [
|
|
||||||
{
|
|
||||||
type: 'VariableDeclarator',
|
|
||||||
id: {
|
|
||||||
type: 'ObjectPattern',
|
|
||||||
properties: helpers.map((h) => ({
|
|
||||||
type: 'Property',
|
|
||||||
method: false,
|
|
||||||
shorthand: false,
|
|
||||||
computed: false,
|
|
||||||
key: { type: 'Identifier', name: h.name },
|
|
||||||
value: h.alias,
|
|
||||||
kind: 'init'
|
|
||||||
}))
|
|
||||||
},
|
|
||||||
init: x`require("${internal_path}")`
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
const internal_globals = get_internal_globals(globals, helpers);
|
|
||||||
|
|
||||||
const user_requires = imports.map((node) => {
|
|
||||||
const init = x`require("${edit_source(node.source.value, sveltePath)}")`;
|
|
||||||
if (node.specifiers.length === 0) {
|
|
||||||
return b`${init};`;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
type: 'VariableDeclaration',
|
|
||||||
kind: 'const',
|
|
||||||
declarations: [
|
|
||||||
{
|
|
||||||
type: 'VariableDeclarator',
|
|
||||||
id:
|
|
||||||
node.specifiers[0].type === 'ImportNamespaceSpecifier'
|
|
||||||
? { type: 'Identifier', name: node.specifiers[0].local.name }
|
|
||||||
: {
|
|
||||||
type: 'ObjectPattern',
|
|
||||||
properties: node.specifiers.map((s) => ({
|
|
||||||
type: 'Property',
|
|
||||||
method: false,
|
|
||||||
shorthand: false,
|
|
||||||
computed: false,
|
|
||||||
key:
|
|
||||||
s.type === 'ImportSpecifier'
|
|
||||||
? s.imported
|
|
||||||
: { type: 'Identifier', name: 'default' },
|
|
||||||
value: s.local,
|
|
||||||
kind: 'init'
|
|
||||||
}))
|
|
||||||
},
|
|
||||||
init
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const exports = module_exports.map(
|
|
||||||
(x) =>
|
|
||||||
b`exports.${{ type: 'Identifier', name: x.as }} = ${{ type: 'Identifier', name: x.name }};`
|
|
||||||
);
|
|
||||||
|
|
||||||
const user_exports_from = exports_from.map((node) => {
|
|
||||||
const init = x`require("${edit_source(node.source.value, sveltePath)}")`;
|
|
||||||
return node.specifiers.map((specifier) => {
|
|
||||||
return b`exports.${specifier.exported} = ${init}.${specifier.local};`;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
program.body = b`
|
|
||||||
/* ${banner} */
|
|
||||||
|
|
||||||
"use strict";
|
|
||||||
${internal_requires}
|
|
||||||
${internal_globals}
|
|
||||||
${user_requires}
|
|
||||||
${user_exports_from}
|
|
||||||
|
|
||||||
${program.body}
|
|
||||||
|
|
||||||
exports.default = ${name};
|
|
||||||
${exports}
|
|
||||||
`;
|
|
||||||
}
|
|
@ -1,8 +1,10 @@
|
|||||||
import { Node } from 'estree';
|
|
||||||
|
|
||||||
export const UNKNOWN = {};
|
export const UNKNOWN = {};
|
||||||
|
|
||||||
export function gather_possible_values(node: Node, set: Set<string | {}>) {
|
/**
|
||||||
|
* @param {import("estree").Node} node
|
||||||
|
* @param {Set<string | {}>} set
|
||||||
|
*/
|
||||||
|
export function gather_possible_values(node, set) {
|
||||||
if (node.type === 'Literal') {
|
if (node.type === 'Literal') {
|
||||||
set.add(node.value);
|
set.add(node.value);
|
||||||
} else if (node.type === 'ConditionalExpression') {
|
} else if (node.type === 'ConditionalExpression') {
|
@ -0,0 +1,2 @@
|
|||||||
|
// This file is automatically generated
|
||||||
|
export default new Set(["HtmlTag","HtmlTagHydration","ResizeObserverSingleton","SvelteComponent","SvelteComponentDev","SvelteComponentTyped","SvelteElement","action_destroyer","add_attribute","add_classes","add_flush_callback","add_iframe_resize_listener","add_location","add_render_callback","add_styles","add_transform","afterUpdate","append","append_dev","append_empty_stylesheet","append_hydration","append_hydration_dev","append_styles","assign","attr","attr_dev","attribute_to_object","beforeUpdate","bind","binding_callbacks","blank_object","bubble","check_outros","children","claim_comment","claim_component","claim_element","claim_html_tag","claim_space","claim_svg_element","claim_text","clear_loops","comment","component_subscribe","compute_rest_props","compute_slots","construct_svelte_component","construct_svelte_component_dev","contenteditable_truthy_values","createEventDispatcher","create_animation","create_bidirectional_transition","create_component","create_custom_element","create_in_transition","create_out_transition","create_slot","create_ssr_component","current_component","custom_event","dataset_dev","debug","destroy_block","destroy_component","destroy_each","detach","detach_after_dev","detach_before_dev","detach_between_dev","detach_dev","dirty_components","dispatch_dev","each","element","element_is","empty","end_hydrating","escape","escape_attribute_value","escape_object","exclude_internal_props","fix_and_destroy_block","fix_and_outro_and_destroy_block","fix_position","flush","flush_render_callbacks","getAllContexts","getContext","get_all_dirty_from_scope","get_binding_group_value","get_current_component","get_custom_elements_slots","get_root_for_style","get_slot_changes","get_spread_object","get_spread_update","get_store_value","get_svelte_dataset","globals","group_outros","handle_promise","hasContext","has_prop","head_selector","identity","init","init_binding_group","init_binding_group_dynamic","insert","insert_dev","insert_hydration","insert_hydration_dev","intros","invalid_attribute_name_character","is_client","is_crossorigin","is_empty","is_function","is_promise","is_void","listen","listen_dev","loop","loop_guard","merge_ssr_styles","missing_component","mount_component","noop","not_equal","now","null_to_empty","object_without_properties","onDestroy","onMount","once","outro_and_destroy_block","prevent_default","prop_dev","query_selector_all","raf","resize_observer_border_box","resize_observer_content_box","resize_observer_device_pixel_content_box","run","run_all","safe_not_equal","schedule_update","select_multiple_value","select_option","select_options","select_value","self","setContext","set_attributes","set_current_component","set_custom_element_data","set_custom_element_data_map","set_data","set_data_contenteditable","set_data_contenteditable_dev","set_data_dev","set_data_maybe_contenteditable","set_data_maybe_contenteditable_dev","set_dynamic_element_data","set_input_type","set_input_value","set_now","set_raf","set_store_value","set_style","set_svg_attributes","space","split_css_unit","spread","src_url_equal","start_hydrating","stop_immediate_propagation","stop_propagation","subscribe","svg_element","text","tick","time_ranges_to_array","to_number","toggle_class","transition_in","transition_out","trusted","update_await_block_branch","update_keyed_each","update_slot","update_slot_base","validate_component","validate_dynamic_element","validate_each_argument","validate_each_keys","validate_slots","validate_store","validate_void_dynamic_element","xlink_attr"]);
|
@ -0,0 +1,36 @@
|
|||||||
|
import Node from './shared/Node.js';
|
||||||
|
import Expression from './shared/Expression.js';
|
||||||
|
|
||||||
|
/** @extends Node<'Action'> */
|
||||||
|
export default class Action extends Node {
|
||||||
|
/** @type {string} */
|
||||||
|
name;
|
||||||
|
|
||||||
|
/** @type {import('./shared/Expression.js').default} */
|
||||||
|
expression;
|
||||||
|
|
||||||
|
/** @type {boolean} */
|
||||||
|
uses_context;
|
||||||
|
|
||||||
|
/** @type {import('./shared/TemplateScope.js').default} */
|
||||||
|
template_scope;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('../Component.js').default} component *
|
||||||
|
* @param {import('./shared/Node.js').default} parent *
|
||||||
|
* @param {import('./shared/TemplateScope.js').default} scope *
|
||||||
|
* @param {import('../../interfaces.js').Directive} info undefined
|
||||||
|
*/
|
||||||
|
constructor(component, parent, scope, info) {
|
||||||
|
super(component, parent, scope, info);
|
||||||
|
const object = info.name.split('.')[0];
|
||||||
|
component.warn_if_undefined(object, info, scope);
|
||||||
|
this.name = info.name;
|
||||||
|
component.add_reference(/** @type {any} */ (this), object);
|
||||||
|
this.expression = info.expression
|
||||||
|
? new Expression(component, this, scope, info.expression)
|
||||||
|
: null;
|
||||||
|
this.template_scope = scope;
|
||||||
|
this.uses_context = this.expression && this.expression.uses_context;
|
||||||
|
}
|
||||||
|
}
|
@ -1,31 +0,0 @@
|
|||||||
import Node from './shared/Node';
|
|
||||||
import Expression from './shared/Expression';
|
|
||||||
import Component from '../Component';
|
|
||||||
import TemplateScope from './shared/TemplateScope';
|
|
||||||
import { Directive } from '../../interfaces';
|
|
||||||
|
|
||||||
export default class Action extends Node {
|
|
||||||
type: 'Action';
|
|
||||||
name: string;
|
|
||||||
expression: Expression;
|
|
||||||
uses_context: boolean;
|
|
||||||
template_scope: TemplateScope;
|
|
||||||
|
|
||||||
constructor(component: Component, parent: Node, scope: TemplateScope, info: Directive) {
|
|
||||||
super(component, parent, scope, info);
|
|
||||||
|
|
||||||
const object = info.name.split('.')[0];
|
|
||||||
component.warn_if_undefined(object, info, scope);
|
|
||||||
|
|
||||||
this.name = info.name;
|
|
||||||
component.add_reference(this as any, object);
|
|
||||||
|
|
||||||
this.expression = info.expression
|
|
||||||
? new Expression(component, this, scope, info.expression)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
this.template_scope = scope;
|
|
||||||
|
|
||||||
this.uses_context = this.expression && this.expression.uses_context;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,43 @@
|
|||||||
|
import Node from './shared/Node.js';
|
||||||
|
import Expression from './shared/Expression.js';
|
||||||
|
import compiler_errors from '../compiler_errors.js';
|
||||||
|
|
||||||
|
/** @extends Node<'Animation'> */
|
||||||
|
export default class Animation extends Node {
|
||||||
|
/** @type {string} */
|
||||||
|
name;
|
||||||
|
|
||||||
|
/** @type {import('./shared/Expression.js').default} */
|
||||||
|
expression;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('../Component.js').default} component *
|
||||||
|
* @param {import('./Element.js').default} parent *
|
||||||
|
* @param {import('./shared/TemplateScope.js').default} scope *
|
||||||
|
* @param {import('../../interfaces.js').TemplateNode} info undefined
|
||||||
|
*/
|
||||||
|
constructor(component, parent, scope, info) {
|
||||||
|
super(component, parent, scope, info);
|
||||||
|
component.warn_if_undefined(info.name, info, scope);
|
||||||
|
this.name = info.name;
|
||||||
|
component.add_reference(/** @type {any} */ (this), info.name.split('.')[0]);
|
||||||
|
if (parent.animation) {
|
||||||
|
component.error(this, compiler_errors.duplicate_animation);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const block = parent.parent;
|
||||||
|
if (!block || block.type !== 'EachBlock') {
|
||||||
|
// TODO can we relax the 'immediate child' rule?
|
||||||
|
component.error(this, compiler_errors.invalid_animation_immediate);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!block.key) {
|
||||||
|
component.error(this, compiler_errors.invalid_animation_key);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/** @type {import('./EachBlock.js').default} */ (block).has_animation = true;
|
||||||
|
this.expression = info.expression
|
||||||
|
? new Expression(component, this, scope, info.expression, true)
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
}
|
@ -1,46 +0,0 @@
|
|||||||
import Node from './shared/Node';
|
|
||||||
import Expression from './shared/Expression';
|
|
||||||
import Component from '../Component';
|
|
||||||
import TemplateScope from './shared/TemplateScope';
|
|
||||||
import { TemplateNode } from '../../interfaces';
|
|
||||||
import Element from './Element';
|
|
||||||
import EachBlock from './EachBlock';
|
|
||||||
import compiler_errors from '../compiler_errors';
|
|
||||||
|
|
||||||
export default class Animation extends Node {
|
|
||||||
type: 'Animation';
|
|
||||||
name: string;
|
|
||||||
expression: Expression;
|
|
||||||
|
|
||||||
constructor(component: Component, parent: Element, scope: TemplateScope, info: TemplateNode) {
|
|
||||||
super(component, parent, scope, info);
|
|
||||||
|
|
||||||
component.warn_if_undefined(info.name, info, scope);
|
|
||||||
|
|
||||||
this.name = info.name;
|
|
||||||
component.add_reference(this as any, info.name.split('.')[0]);
|
|
||||||
|
|
||||||
if (parent.animation) {
|
|
||||||
component.error(this, compiler_errors.duplicate_animation);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const block = parent.parent;
|
|
||||||
if (!block || block.type !== 'EachBlock') {
|
|
||||||
// TODO can we relax the 'immediate child' rule?
|
|
||||||
component.error(this, compiler_errors.invalid_animation_immediate);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!block.key) {
|
|
||||||
component.error(this, compiler_errors.invalid_animation_key);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
(block as EachBlock).has_animation = true;
|
|
||||||
|
|
||||||
this.expression = info.expression
|
|
||||||
? new Expression(component, this, scope, info.expression, true)
|
|
||||||
: null;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,131 @@
|
|||||||
|
import { string_literal } from '../utils/stringify.js';
|
||||||
|
import add_to_set from '../utils/add_to_set.js';
|
||||||
|
import Node from './shared/Node.js';
|
||||||
|
import Expression from './shared/Expression.js';
|
||||||
|
import { x } from 'code-red';
|
||||||
|
|
||||||
|
/** @extends Node<'Attribute' | 'Spread', import('./Element.js').default> */
|
||||||
|
export default class Attribute extends Node {
|
||||||
|
/** @type {import('./shared/TemplateScope.js').default} */
|
||||||
|
scope;
|
||||||
|
|
||||||
|
/** @type {string} */
|
||||||
|
name;
|
||||||
|
|
||||||
|
/** @type {boolean} */
|
||||||
|
is_spread;
|
||||||
|
|
||||||
|
/** @type {boolean} */
|
||||||
|
is_true;
|
||||||
|
|
||||||
|
/** @type {boolean} */
|
||||||
|
is_static;
|
||||||
|
|
||||||
|
/** @type {import('./shared/Expression.js').default} */
|
||||||
|
expression;
|
||||||
|
|
||||||
|
/** @type {Array<import('./Text.js').default | import('./shared/Expression.js').default>} */
|
||||||
|
chunks;
|
||||||
|
|
||||||
|
/** @type {Set<string>} */
|
||||||
|
dependencies;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('../Component.js').default} component
|
||||||
|
* @param {import('./shared/Node.js').default} parent
|
||||||
|
* @param {import('./shared/TemplateScope.js').default} scope
|
||||||
|
* @param {import('../../interfaces.js').TemplateNode} info
|
||||||
|
*/
|
||||||
|
constructor(component, parent, scope, info) {
|
||||||
|
super(component, parent, scope, info);
|
||||||
|
this.scope = scope;
|
||||||
|
if (info.type === 'Spread') {
|
||||||
|
this.name = null;
|
||||||
|
this.is_spread = true;
|
||||||
|
this.is_true = false;
|
||||||
|
this.expression = new Expression(component, this, scope, info.expression);
|
||||||
|
this.dependencies = this.expression.dependencies;
|
||||||
|
this.chunks = null;
|
||||||
|
this.is_static = false;
|
||||||
|
} else {
|
||||||
|
this.name = info.name;
|
||||||
|
this.is_true = info.value === true;
|
||||||
|
this.is_static = true;
|
||||||
|
this.dependencies = new Set();
|
||||||
|
this.chunks = this.is_true
|
||||||
|
? []
|
||||||
|
: info.value.map(
|
||||||
|
/** @param {any} node */ (node) => {
|
||||||
|
if (node.type === 'Text') return node;
|
||||||
|
this.is_static = false;
|
||||||
|
const expression = new Expression(component, this, scope, node.expression);
|
||||||
|
add_to_set(this.dependencies, expression.dependencies);
|
||||||
|
return expression;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (this.dependencies.size > 0) {
|
||||||
|
parent.cannot_use_innerhtml();
|
||||||
|
parent.not_static_content();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
get_dependencies() {
|
||||||
|
if (this.is_spread) return this.expression.dynamic_dependencies();
|
||||||
|
|
||||||
|
/** @type {Set<string>} */
|
||||||
|
const dependencies = new Set();
|
||||||
|
this.chunks.forEach(
|
||||||
|
/** @param {any} chunk */ (chunk) => {
|
||||||
|
if (chunk.type === 'Expression') {
|
||||||
|
add_to_set(dependencies, chunk.dynamic_dependencies());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return Array.from(dependencies);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {any} block */
|
||||||
|
get_value(block) {
|
||||||
|
if (this.is_true) return x`true`;
|
||||||
|
if (this.chunks.length === 0) return x`""`;
|
||||||
|
if (this.chunks.length === 1) {
|
||||||
|
return this.chunks[0].type === 'Text'
|
||||||
|
? string_literal(/** @type {import('./Text.js').default} */ (this.chunks[0]).data)
|
||||||
|
: /** @type {import('./shared/Expression.js').default} */ (this.chunks[0]).manipulate(
|
||||||
|
block
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let expression = this.chunks
|
||||||
|
.map(
|
||||||
|
/** @param {any} chunk */ (chunk) =>
|
||||||
|
chunk.type === 'Text' ? string_literal(chunk.data) : chunk.manipulate(block)
|
||||||
|
)
|
||||||
|
.reduce(
|
||||||
|
/**
|
||||||
|
* @param {any} lhs
|
||||||
|
* @param {any} rhs
|
||||||
|
*/ (lhs, rhs) => x`${lhs} + ${rhs}`
|
||||||
|
);
|
||||||
|
if (this.chunks[0].type !== 'Text') {
|
||||||
|
expression = x`"" + ${expression}`;
|
||||||
|
}
|
||||||
|
return expression;
|
||||||
|
}
|
||||||
|
get_static_value() {
|
||||||
|
if (!this.is_static) return null;
|
||||||
|
return this.is_true
|
||||||
|
? true
|
||||||
|
: this.chunks[0]
|
||||||
|
? // method should be called only when `is_static = true`
|
||||||
|
/** @type {import('./Text.js').default} */ (this.chunks[0]).data
|
||||||
|
: '';
|
||||||
|
}
|
||||||
|
should_cache() {
|
||||||
|
return this.is_static
|
||||||
|
? false
|
||||||
|
: this.chunks.length === 1
|
||||||
|
? // @ts-ignore todo: probably error
|
||||||
|
this.chunks[0].node.type !== 'Identifier' || this.scope.names.has(this.chunks[0].node.name)
|
||||||
|
: true;
|
||||||
|
}
|
||||||
|
}
|
@ -1,124 +0,0 @@
|
|||||||
import { string_literal } from '../utils/stringify';
|
|
||||||
import add_to_set from '../utils/add_to_set';
|
|
||||||
import Component from '../Component';
|
|
||||||
import Node from './shared/Node';
|
|
||||||
import Element from './Element';
|
|
||||||
import Text from './Text';
|
|
||||||
import Expression from './shared/Expression';
|
|
||||||
import TemplateScope from './shared/TemplateScope';
|
|
||||||
import { x } from 'code-red';
|
|
||||||
import { TemplateNode } from '../../interfaces';
|
|
||||||
|
|
||||||
export default class Attribute extends Node {
|
|
||||||
type: 'Attribute' | 'Spread';
|
|
||||||
start: number;
|
|
||||||
end: number;
|
|
||||||
scope: TemplateScope;
|
|
||||||
|
|
||||||
component: Component;
|
|
||||||
parent: Element;
|
|
||||||
name: string;
|
|
||||||
is_spread: boolean;
|
|
||||||
is_true: boolean;
|
|
||||||
is_static: boolean;
|
|
||||||
expression?: Expression;
|
|
||||||
chunks: Array<Text | Expression>;
|
|
||||||
dependencies: Set<string>;
|
|
||||||
|
|
||||||
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
|
|
||||||
super(component, parent, scope, info);
|
|
||||||
this.scope = scope;
|
|
||||||
|
|
||||||
if (info.type === 'Spread') {
|
|
||||||
this.name = null;
|
|
||||||
this.is_spread = true;
|
|
||||||
this.is_true = false;
|
|
||||||
|
|
||||||
this.expression = new Expression(component, this, scope, info.expression);
|
|
||||||
this.dependencies = this.expression.dependencies;
|
|
||||||
this.chunks = null;
|
|
||||||
|
|
||||||
this.is_static = false;
|
|
||||||
} else {
|
|
||||||
this.name = info.name;
|
|
||||||
this.is_true = info.value === true;
|
|
||||||
this.is_static = true;
|
|
||||||
|
|
||||||
this.dependencies = new Set();
|
|
||||||
|
|
||||||
this.chunks = this.is_true
|
|
||||||
? []
|
|
||||||
: info.value.map((node) => {
|
|
||||||
if (node.type === 'Text') return node;
|
|
||||||
|
|
||||||
this.is_static = false;
|
|
||||||
|
|
||||||
const expression = new Expression(component, this, scope, node.expression);
|
|
||||||
|
|
||||||
add_to_set(this.dependencies, expression.dependencies);
|
|
||||||
return expression;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.dependencies.size > 0) {
|
|
||||||
parent.cannot_use_innerhtml();
|
|
||||||
parent.not_static_content();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get_dependencies() {
|
|
||||||
if (this.is_spread) return this.expression.dynamic_dependencies();
|
|
||||||
|
|
||||||
const dependencies: Set<string> = new Set();
|
|
||||||
this.chunks.forEach((chunk) => {
|
|
||||||
if (chunk.type === 'Expression') {
|
|
||||||
add_to_set(dependencies, chunk.dynamic_dependencies());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return Array.from(dependencies);
|
|
||||||
}
|
|
||||||
|
|
||||||
get_value(block) {
|
|
||||||
if (this.is_true) return x`true`;
|
|
||||||
if (this.chunks.length === 0) return x`""`;
|
|
||||||
|
|
||||||
if (this.chunks.length === 1) {
|
|
||||||
return this.chunks[0].type === 'Text'
|
|
||||||
? string_literal((this.chunks[0] as Text).data)
|
|
||||||
: (this.chunks[0] as Expression).manipulate(block);
|
|
||||||
}
|
|
||||||
|
|
||||||
let expression = this.chunks
|
|
||||||
.map((chunk) =>
|
|
||||||
chunk.type === 'Text' ? string_literal(chunk.data) : chunk.manipulate(block)
|
|
||||||
)
|
|
||||||
.reduce((lhs, rhs) => x`${lhs} + ${rhs}`);
|
|
||||||
|
|
||||||
if (this.chunks[0].type !== 'Text') {
|
|
||||||
expression = x`"" + ${expression}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return expression;
|
|
||||||
}
|
|
||||||
|
|
||||||
get_static_value() {
|
|
||||||
if (!this.is_static) return null;
|
|
||||||
|
|
||||||
return this.is_true
|
|
||||||
? true
|
|
||||||
: this.chunks[0]
|
|
||||||
? // method should be called only when `is_static = true`
|
|
||||||
(this.chunks[0] as Text).data
|
|
||||||
: '';
|
|
||||||
}
|
|
||||||
|
|
||||||
should_cache() {
|
|
||||||
return this.is_static
|
|
||||||
? false
|
|
||||||
: this.chunks.length === 1
|
|
||||||
? // @ts-ignore todo: probably error
|
|
||||||
this.chunks[0].node.type !== 'Identifier' || this.scope.names.has(this.chunks[0].node.name)
|
|
||||||
: true;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,74 @@
|
|||||||
|
import Node from './shared/Node.js';
|
||||||
|
import PendingBlock from './PendingBlock.js';
|
||||||
|
import ThenBlock from './ThenBlock.js';
|
||||||
|
import CatchBlock from './CatchBlock.js';
|
||||||
|
import Expression from './shared/Expression.js';
|
||||||
|
import { unpack_destructuring } from './shared/Context.js';
|
||||||
|
|
||||||
|
/** @extends Node<'AwaitBlock'> */
|
||||||
|
export default class AwaitBlock extends Node {
|
||||||
|
/** @type {import('./shared/Expression.js').default} */
|
||||||
|
expression;
|
||||||
|
|
||||||
|
/** @type {import('./shared/Context.js').Context[]} */
|
||||||
|
then_contexts;
|
||||||
|
|
||||||
|
/** @type {import('./shared/Context.js').Context[]} */
|
||||||
|
catch_contexts;
|
||||||
|
|
||||||
|
/** @type {import('estree').Node | null} */
|
||||||
|
then_node;
|
||||||
|
|
||||||
|
/** @type {import('estree').Node | null} */
|
||||||
|
catch_node;
|
||||||
|
|
||||||
|
/** @type {import('./PendingBlock.js').default} */
|
||||||
|
pending;
|
||||||
|
|
||||||
|
/** @type {import('./ThenBlock.js').default} */
|
||||||
|
then;
|
||||||
|
|
||||||
|
/** @type {import('./CatchBlock.js').default} */
|
||||||
|
catch;
|
||||||
|
|
||||||
|
/** @type {Map<string, import('estree').Node>} */
|
||||||
|
context_rest_properties = new Map();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('../Component.js').default} component
|
||||||
|
* @param {import('./shared/Node.js').default} parent
|
||||||
|
* @param {import('./shared/TemplateScope.js').default} scope
|
||||||
|
* @param {import('../../interfaces.js').TemplateNode} info
|
||||||
|
*/
|
||||||
|
constructor(component, parent, scope, info) {
|
||||||
|
super(component, parent, scope, info);
|
||||||
|
this.cannot_use_innerhtml();
|
||||||
|
this.not_static_content();
|
||||||
|
this.expression = new Expression(component, this, scope, info.expression);
|
||||||
|
this.then_node = info.value;
|
||||||
|
this.catch_node = info.error;
|
||||||
|
if (this.then_node) {
|
||||||
|
this.then_contexts = [];
|
||||||
|
unpack_destructuring({
|
||||||
|
contexts: this.then_contexts,
|
||||||
|
node: info.value,
|
||||||
|
scope,
|
||||||
|
component,
|
||||||
|
context_rest_properties: this.context_rest_properties
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (this.catch_node) {
|
||||||
|
this.catch_contexts = [];
|
||||||
|
unpack_destructuring({
|
||||||
|
contexts: this.catch_contexts,
|
||||||
|
node: info.error,
|
||||||
|
scope,
|
||||||
|
component,
|
||||||
|
context_rest_properties: this.context_rest_properties
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.pending = new PendingBlock(component, this, scope, info.pending);
|
||||||
|
this.then = new ThenBlock(component, this, scope, info.then);
|
||||||
|
this.catch = new CatchBlock(component, this, scope, info.catch);
|
||||||
|
}
|
||||||
|
}
|
@ -1,64 +0,0 @@
|
|||||||
import Node from './shared/Node';
|
|
||||||
import PendingBlock from './PendingBlock';
|
|
||||||
import ThenBlock from './ThenBlock';
|
|
||||||
import CatchBlock from './CatchBlock';
|
|
||||||
import Expression from './shared/Expression';
|
|
||||||
import Component from '../Component';
|
|
||||||
import TemplateScope from './shared/TemplateScope';
|
|
||||||
import { TemplateNode } from '../../interfaces';
|
|
||||||
import { Context, unpack_destructuring } from './shared/Context';
|
|
||||||
import { Node as ESTreeNode } from 'estree';
|
|
||||||
|
|
||||||
export default class AwaitBlock extends Node {
|
|
||||||
type: 'AwaitBlock';
|
|
||||||
expression: Expression;
|
|
||||||
|
|
||||||
then_contexts: Context[];
|
|
||||||
catch_contexts: Context[];
|
|
||||||
|
|
||||||
then_node: ESTreeNode | null;
|
|
||||||
catch_node: ESTreeNode | null;
|
|
||||||
|
|
||||||
pending: PendingBlock;
|
|
||||||
then: ThenBlock;
|
|
||||||
catch: CatchBlock;
|
|
||||||
|
|
||||||
context_rest_properties: Map<string, ESTreeNode> = new Map();
|
|
||||||
|
|
||||||
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
|
|
||||||
super(component, parent, scope, info);
|
|
||||||
this.cannot_use_innerhtml();
|
|
||||||
this.not_static_content();
|
|
||||||
|
|
||||||
this.expression = new Expression(component, this, scope, info.expression);
|
|
||||||
|
|
||||||
this.then_node = info.value;
|
|
||||||
this.catch_node = info.error;
|
|
||||||
|
|
||||||
if (this.then_node) {
|
|
||||||
this.then_contexts = [];
|
|
||||||
unpack_destructuring({
|
|
||||||
contexts: this.then_contexts,
|
|
||||||
node: info.value,
|
|
||||||
scope,
|
|
||||||
component,
|
|
||||||
context_rest_properties: this.context_rest_properties
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.catch_node) {
|
|
||||||
this.catch_contexts = [];
|
|
||||||
unpack_destructuring({
|
|
||||||
contexts: this.catch_contexts,
|
|
||||||
node: info.error,
|
|
||||||
scope,
|
|
||||||
component,
|
|
||||||
context_rest_properties: this.context_rest_properties
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.pending = new PendingBlock(component, this, scope, info.pending);
|
|
||||||
this.then = new ThenBlock(component, this, scope, info.then);
|
|
||||||
this.catch = new CatchBlock(component, this, scope, info.catch);
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,136 @@
|
|||||||
|
import Node from './shared/Node.js';
|
||||||
|
import get_object from '../utils/get_object.js';
|
||||||
|
import Expression from './shared/Expression.js';
|
||||||
|
import { regex_dimensions, regex_box_size } from '../../utils/patterns.js';
|
||||||
|
import { clone } from '../../utils/clone.js';
|
||||||
|
import compiler_errors from '../compiler_errors.js';
|
||||||
|
import compiler_warnings from '../compiler_warnings.js';
|
||||||
|
|
||||||
|
// TODO this should live in a specific binding
|
||||||
|
const read_only_media_attributes = new Set([
|
||||||
|
'duration',
|
||||||
|
'buffered',
|
||||||
|
'seekable',
|
||||||
|
'played',
|
||||||
|
'seeking',
|
||||||
|
'ended',
|
||||||
|
'videoHeight',
|
||||||
|
'videoWidth',
|
||||||
|
'naturalWidth',
|
||||||
|
'naturalHeight',
|
||||||
|
'readyState'
|
||||||
|
]);
|
||||||
|
|
||||||
|
/** @extends Node<'Binding'> */
|
||||||
|
export default class Binding extends Node {
|
||||||
|
/** @type {string} */
|
||||||
|
name;
|
||||||
|
|
||||||
|
/** @type {import('./shared/Expression.js').default} */
|
||||||
|
expression;
|
||||||
|
|
||||||
|
/** @type {import('estree').Node} */
|
||||||
|
raw_expression; // TODO exists only for bind:this — is there a more elegant solution?
|
||||||
|
|
||||||
|
/** @type {boolean} */
|
||||||
|
is_contextual;
|
||||||
|
|
||||||
|
/** @type {boolean} */
|
||||||
|
is_readonly;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('../Component.js').default} component
|
||||||
|
* @param {import('./Element.js').default | import('./InlineComponent.js').default | import('./Window.js').default | import('./Document.js').default} parent
|
||||||
|
* @param {import('./shared/TemplateScope.js').default} scope
|
||||||
|
* @param {import('../../interfaces.js').TemplateNode} info
|
||||||
|
*/
|
||||||
|
constructor(component, parent, scope, info) {
|
||||||
|
super(component, parent, scope, info);
|
||||||
|
if (info.expression.type !== 'Identifier' && info.expression.type !== 'MemberExpression') {
|
||||||
|
component.error(info, compiler_errors.invalid_directive_value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.name = info.name;
|
||||||
|
this.expression = new Expression(component, this, scope, info.expression);
|
||||||
|
this.raw_expression = clone(info.expression);
|
||||||
|
const { name } = get_object(this.expression.node);
|
||||||
|
this.is_contextual = Array.from(this.expression.references).some(
|
||||||
|
/** @param {any} name */ (name) => scope.names.has(name)
|
||||||
|
);
|
||||||
|
if (this.is_contextual) this.validate_binding_rest_properties(scope);
|
||||||
|
// make sure we track this as a mutable ref
|
||||||
|
if (scope.is_let(name)) {
|
||||||
|
component.error(this, compiler_errors.invalid_binding_let);
|
||||||
|
return;
|
||||||
|
} else if (scope.names.has(name)) {
|
||||||
|
if (scope.is_await(name)) {
|
||||||
|
component.error(this, compiler_errors.invalid_binding_await);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (scope.is_const(name)) {
|
||||||
|
component.error(this, compiler_errors.invalid_binding_const);
|
||||||
|
}
|
||||||
|
scope.dependencies_for_name.get(name).forEach(
|
||||||
|
/** @param {any} name */ (name) => {
|
||||||
|
const variable = component.var_lookup.get(name);
|
||||||
|
if (variable) {
|
||||||
|
variable.mutated = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const variable = component.var_lookup.get(name);
|
||||||
|
if (!variable || variable.global) {
|
||||||
|
component.error(
|
||||||
|
/** @type {any} */ (this.expression.node),
|
||||||
|
compiler_errors.binding_undeclared(name)
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
variable[this.expression.node.type === 'MemberExpression' ? 'mutated' : 'reassigned'] = true;
|
||||||
|
if (info.expression.type === 'Identifier' && !variable.writable) {
|
||||||
|
component.error(
|
||||||
|
/** @type {any} */ (this.expression.node),
|
||||||
|
compiler_errors.invalid_binding_writable
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const type = parent.get_static_attribute_value('type');
|
||||||
|
this.is_readonly =
|
||||||
|
regex_dimensions.test(this.name) ||
|
||||||
|
regex_box_size.test(this.name) ||
|
||||||
|
(isElement(parent) &&
|
||||||
|
((parent.is_media_node() && read_only_media_attributes.has(this.name)) ||
|
||||||
|
(parent.name === 'input' && type === 'file'))) /* TODO others? */;
|
||||||
|
}
|
||||||
|
is_readonly_media_attribute() {
|
||||||
|
return read_only_media_attributes.has(this.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {import('./shared/TemplateScope.js').default} scope */
|
||||||
|
validate_binding_rest_properties(scope) {
|
||||||
|
this.expression.references.forEach(
|
||||||
|
/** @param {any} name */ (name) => {
|
||||||
|
const each_block = scope.get_owner(name);
|
||||||
|
if (each_block && each_block.type === 'EachBlock') {
|
||||||
|
const rest_node = each_block.context_rest_properties.get(name);
|
||||||
|
if (rest_node) {
|
||||||
|
this.component.warn(
|
||||||
|
/** @type {any} */ (rest_node),
|
||||||
|
compiler_warnings.invalid_rest_eachblock_binding(name)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('./shared/Node.js').default} node
|
||||||
|
* @returns {node is import('./Element.js').default}
|
||||||
|
*/
|
||||||
|
function isElement(node) {
|
||||||
|
return !!(/** @type {any} */ (node).is_media_node);
|
||||||
|
}
|
@ -1,131 +0,0 @@
|
|||||||
import Node from './shared/Node';
|
|
||||||
import get_object from '../utils/get_object';
|
|
||||||
import Expression from './shared/Expression';
|
|
||||||
import Component from '../Component';
|
|
||||||
import TemplateScope from './shared/TemplateScope';
|
|
||||||
import { regex_dimensions, regex_box_size } from '../../utils/patterns';
|
|
||||||
import { Node as ESTreeNode } from 'estree';
|
|
||||||
import { TemplateNode } from '../../interfaces';
|
|
||||||
import Element from './Element';
|
|
||||||
import InlineComponent from './InlineComponent';
|
|
||||||
import Window from './Window';
|
|
||||||
import Document from './Document';
|
|
||||||
import { clone } from '../../utils/clone';
|
|
||||||
import compiler_errors from '../compiler_errors';
|
|
||||||
import compiler_warnings from '../compiler_warnings';
|
|
||||||
|
|
||||||
// TODO this should live in a specific binding
|
|
||||||
const read_only_media_attributes = new Set([
|
|
||||||
'duration',
|
|
||||||
'buffered',
|
|
||||||
'seekable',
|
|
||||||
'played',
|
|
||||||
'seeking',
|
|
||||||
'ended',
|
|
||||||
'videoHeight',
|
|
||||||
'videoWidth',
|
|
||||||
'naturalWidth',
|
|
||||||
'naturalHeight',
|
|
||||||
'readyState'
|
|
||||||
]);
|
|
||||||
|
|
||||||
export default class Binding extends Node {
|
|
||||||
type: 'Binding';
|
|
||||||
name: string;
|
|
||||||
expression: Expression;
|
|
||||||
raw_expression: ESTreeNode; // TODO exists only for bind:this — is there a more elegant solution?
|
|
||||||
is_contextual: boolean;
|
|
||||||
is_readonly: boolean;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
component: Component,
|
|
||||||
parent: Element | InlineComponent | Window | Document,
|
|
||||||
scope: TemplateScope,
|
|
||||||
info: TemplateNode
|
|
||||||
) {
|
|
||||||
super(component, parent, scope, info);
|
|
||||||
|
|
||||||
if (info.expression.type !== 'Identifier' && info.expression.type !== 'MemberExpression') {
|
|
||||||
component.error(info, compiler_errors.invalid_directive_value);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.name = info.name;
|
|
||||||
this.expression = new Expression(component, this, scope, info.expression);
|
|
||||||
this.raw_expression = clone(info.expression);
|
|
||||||
|
|
||||||
const { name } = get_object(this.expression.node);
|
|
||||||
|
|
||||||
this.is_contextual = Array.from(this.expression.references).some((name) =>
|
|
||||||
scope.names.has(name)
|
|
||||||
);
|
|
||||||
if (this.is_contextual) this.validate_binding_rest_properties(scope);
|
|
||||||
|
|
||||||
// make sure we track this as a mutable ref
|
|
||||||
if (scope.is_let(name)) {
|
|
||||||
component.error(this, compiler_errors.invalid_binding_let);
|
|
||||||
return;
|
|
||||||
} else if (scope.names.has(name)) {
|
|
||||||
if (scope.is_await(name)) {
|
|
||||||
component.error(this, compiler_errors.invalid_binding_await);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (scope.is_const(name)) {
|
|
||||||
component.error(this, compiler_errors.invalid_binding_const);
|
|
||||||
}
|
|
||||||
|
|
||||||
scope.dependencies_for_name.get(name).forEach((name) => {
|
|
||||||
const variable = component.var_lookup.get(name);
|
|
||||||
if (variable) {
|
|
||||||
variable.mutated = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
const variable = component.var_lookup.get(name);
|
|
||||||
|
|
||||||
if (!variable || variable.global) {
|
|
||||||
component.error(this.expression.node as any, compiler_errors.binding_undeclared(name));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
variable[this.expression.node.type === 'MemberExpression' ? 'mutated' : 'reassigned'] = true;
|
|
||||||
|
|
||||||
if (info.expression.type === 'Identifier' && !variable.writable) {
|
|
||||||
component.error(this.expression.node as any, compiler_errors.invalid_binding_writable);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const type = parent.get_static_attribute_value('type');
|
|
||||||
|
|
||||||
this.is_readonly =
|
|
||||||
regex_dimensions.test(this.name) ||
|
|
||||||
regex_box_size.test(this.name) ||
|
|
||||||
(isElement(parent) &&
|
|
||||||
((parent.is_media_node() && read_only_media_attributes.has(this.name)) ||
|
|
||||||
(parent.name === 'input' && type === 'file'))) /* TODO others? */;
|
|
||||||
}
|
|
||||||
|
|
||||||
is_readonly_media_attribute() {
|
|
||||||
return read_only_media_attributes.has(this.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
validate_binding_rest_properties(scope: TemplateScope) {
|
|
||||||
this.expression.references.forEach((name) => {
|
|
||||||
const each_block = scope.get_owner(name);
|
|
||||||
if (each_block && each_block.type === 'EachBlock') {
|
|
||||||
const rest_node = each_block.context_rest_properties.get(name);
|
|
||||||
if (rest_node) {
|
|
||||||
this.component.warn(
|
|
||||||
rest_node as any,
|
|
||||||
compiler_warnings.invalid_rest_eachblock_binding(name)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function isElement(node: Node): node is Element {
|
|
||||||
return !!(node as any).is_media_node;
|
|
||||||
}
|
|
@ -0,0 +1,33 @@
|
|||||||
|
import Node from './shared/Node.js';
|
||||||
|
import EventHandler from './EventHandler.js';
|
||||||
|
import Action from './Action.js';
|
||||||
|
|
||||||
|
/** @extends Node<'Body'> */
|
||||||
|
export default class Body extends Node {
|
||||||
|
/** @type {import('./EventHandler.js').default[]} */
|
||||||
|
handlers = [];
|
||||||
|
|
||||||
|
/** @type {import('./Action.js').default[]} */
|
||||||
|
actions = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('../Component.js').default} component
|
||||||
|
* @param {import('./shared/Node.js').default} parent
|
||||||
|
* @param {import('./shared/TemplateScope.js').default} scope
|
||||||
|
* @param {import('../../interfaces.js').Element} info
|
||||||
|
*/
|
||||||
|
constructor(component, parent, scope, info) {
|
||||||
|
super(component, parent, scope, info);
|
||||||
|
info.attributes.forEach(
|
||||||
|
/** @param {any} node */ (node) => {
|
||||||
|
if (node.type === 'EventHandler') {
|
||||||
|
this.handlers.push(new EventHandler(component, this, scope, node));
|
||||||
|
} else if (node.type === 'Action') {
|
||||||
|
this.actions.push(new Action(component, this, scope, node));
|
||||||
|
} else {
|
||||||
|
// TODO there shouldn't be anything else here...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,26 +0,0 @@
|
|||||||
import Node from './shared/Node';
|
|
||||||
import EventHandler from './EventHandler';
|
|
||||||
import Action from './Action';
|
|
||||||
import Component from '../Component';
|
|
||||||
import TemplateScope from './shared/TemplateScope';
|
|
||||||
import { Element } from '../../interfaces';
|
|
||||||
|
|
||||||
export default class Body extends Node {
|
|
||||||
type: 'Body';
|
|
||||||
handlers: EventHandler[] = [];
|
|
||||||
actions: Action[] = [];
|
|
||||||
|
|
||||||
constructor(component: Component, parent: Node, scope: TemplateScope, info: Element) {
|
|
||||||
super(component, parent, scope, info);
|
|
||||||
|
|
||||||
info.attributes.forEach((node) => {
|
|
||||||
if (node.type === 'EventHandler') {
|
|
||||||
this.handlers.push(new EventHandler(component, this, scope, node));
|
|
||||||
} else if (node.type === 'Action') {
|
|
||||||
this.actions.push(new Action(component, this, scope, node));
|
|
||||||
} else {
|
|
||||||
// TODO there shouldn't be anything else here...
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,34 @@
|
|||||||
|
import AbstractBlock from './shared/AbstractBlock.js';
|
||||||
|
import get_const_tags from './shared/get_const_tags.js';
|
||||||
|
|
||||||
|
/** @extends AbstractBlock<'CatchBlock'> */
|
||||||
|
export default class CatchBlock extends AbstractBlock {
|
||||||
|
/** @type {import('./shared/TemplateScope.js').default} */
|
||||||
|
scope;
|
||||||
|
|
||||||
|
/** @type {import('./ConstTag.js').default[]} */
|
||||||
|
const_tags;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('../Component.js').default} component
|
||||||
|
* @param {import('./AwaitBlock.js').default} parent
|
||||||
|
* @param {import('./shared/TemplateScope.js').default} scope
|
||||||
|
* @param {import('../../interfaces.js').TemplateNode} info
|
||||||
|
*/
|
||||||
|
constructor(component, parent, scope, info) {
|
||||||
|
super(component, parent, scope, info);
|
||||||
|
this.scope = scope.child();
|
||||||
|
if (parent.catch_node) {
|
||||||
|
parent.catch_contexts.forEach(
|
||||||
|
/** @param {any} context */ (context) => {
|
||||||
|
if (context.type !== 'DestructuredVariable') return;
|
||||||
|
this.scope.add(context.key.name, parent.expression.dependencies, this);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
[this.const_tags, this.children] = get_const_tags(info.children, component, this, parent);
|
||||||
|
if (!info.skip) {
|
||||||
|
this.warn_if_empty_block();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,31 +0,0 @@
|
|||||||
import TemplateScope from './shared/TemplateScope';
|
|
||||||
import AbstractBlock from './shared/AbstractBlock';
|
|
||||||
import AwaitBlock from './AwaitBlock';
|
|
||||||
import Component from '../Component';
|
|
||||||
import { TemplateNode } from '../../interfaces';
|
|
||||||
import get_const_tags from './shared/get_const_tags';
|
|
||||||
import ConstTag from './ConstTag';
|
|
||||||
|
|
||||||
export default class CatchBlock extends AbstractBlock {
|
|
||||||
type: 'CatchBlock';
|
|
||||||
scope: TemplateScope;
|
|
||||||
const_tags: ConstTag[];
|
|
||||||
|
|
||||||
constructor(component: Component, parent: AwaitBlock, scope: TemplateScope, info: TemplateNode) {
|
|
||||||
super(component, parent, scope, info);
|
|
||||||
|
|
||||||
this.scope = scope.child();
|
|
||||||
if (parent.catch_node) {
|
|
||||||
parent.catch_contexts.forEach((context) => {
|
|
||||||
if (context.type !== 'DestructuredVariable') return;
|
|
||||||
this.scope.add(context.key.name, parent.expression.dependencies, this);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
[this.const_tags, this.children] = get_const_tags(info.children, component, this, parent);
|
|
||||||
|
|
||||||
if (!info.skip) {
|
|
||||||
this.warn_if_empty_block();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,25 @@
|
|||||||
|
import Node from './shared/Node.js';
|
||||||
|
import Expression from './shared/Expression.js';
|
||||||
|
|
||||||
|
/** @extends Node<'Class'> */
|
||||||
|
export default class Class extends Node {
|
||||||
|
/** @type {string} */
|
||||||
|
name;
|
||||||
|
|
||||||
|
/** @type {import('./shared/Expression.js').default} */
|
||||||
|
expression;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('../Component.js').default} component
|
||||||
|
* @param {import('./shared/Node.js').default} parent
|
||||||
|
* @param {import('./shared/TemplateScope.js').default} scope
|
||||||
|
* @param {import('../../interfaces.js').TemplateNode} info
|
||||||
|
*/
|
||||||
|
constructor(component, parent, scope, info) {
|
||||||
|
super(component, parent, scope, info);
|
||||||
|
this.name = info.name;
|
||||||
|
this.expression = info.expression
|
||||||
|
? new Expression(component, this, scope, info.expression)
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
}
|
@ -1,21 +0,0 @@
|
|||||||
import Node from './shared/Node';
|
|
||||||
import Expression from './shared/Expression';
|
|
||||||
import { TemplateNode } from '../../interfaces';
|
|
||||||
import TemplateScope from './shared/TemplateScope';
|
|
||||||
import Component from '../Component';
|
|
||||||
|
|
||||||
export default class Class extends Node {
|
|
||||||
type: 'Class';
|
|
||||||
name: string;
|
|
||||||
expression: Expression;
|
|
||||||
|
|
||||||
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
|
|
||||||
super(component, parent, scope, info);
|
|
||||||
|
|
||||||
this.name = info.name;
|
|
||||||
|
|
||||||
this.expression = info.expression
|
|
||||||
? new Expression(component, this, scope, info.expression)
|
|
||||||
: null;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,22 @@
|
|||||||
|
import Node from './shared/Node.js';
|
||||||
|
|
||||||
|
/** @extends Node<'Comment'> */
|
||||||
|
export default class Comment extends Node {
|
||||||
|
/** @type {string} */
|
||||||
|
data;
|
||||||
|
|
||||||
|
/** @type {string[]} */
|
||||||
|
ignores;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('../Component.js').default} component
|
||||||
|
* @param {import('./shared/Node.js').default} parent
|
||||||
|
* @param {import('./shared/TemplateScope.js').default} scope
|
||||||
|
* @param {import('../../interfaces.js').TemplateNode} info
|
||||||
|
*/
|
||||||
|
constructor(component, parent, scope, info) {
|
||||||
|
super(component, parent, scope, info);
|
||||||
|
this.data = info.data;
|
||||||
|
this.ignores = info.ignores;
|
||||||
|
}
|
||||||
|
}
|
@ -1,16 +0,0 @@
|
|||||||
import { TemplateNode } from '../../interfaces';
|
|
||||||
import Component from '../Component';
|
|
||||||
import Node from './shared/Node';
|
|
||||||
import TemplateScope from './shared/TemplateScope';
|
|
||||||
|
|
||||||
export default class Comment extends Node {
|
|
||||||
type: 'Comment';
|
|
||||||
data: string;
|
|
||||||
ignores: string[];
|
|
||||||
|
|
||||||
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
|
|
||||||
super(component, parent, scope, info);
|
|
||||||
this.data = info.data;
|
|
||||||
this.ignores = info.ignores;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,109 @@
|
|||||||
|
import Node from './shared/Node.js';
|
||||||
|
import Expression from './shared/Expression.js';
|
||||||
|
import { unpack_destructuring } from './shared/Context.js';
|
||||||
|
import { walk } from 'estree-walker';
|
||||||
|
import { extract_identifiers } from 'periscopic';
|
||||||
|
import is_reference from 'is-reference';
|
||||||
|
import get_object from '../utils/get_object.js';
|
||||||
|
import compiler_errors from '../compiler_errors.js';
|
||||||
|
|
||||||
|
const allowed_parents = new Set([
|
||||||
|
'EachBlock',
|
||||||
|
'CatchBlock',
|
||||||
|
'ThenBlock',
|
||||||
|
'InlineComponent',
|
||||||
|
'SlotTemplate',
|
||||||
|
'IfBlock',
|
||||||
|
'ElseBlock'
|
||||||
|
]);
|
||||||
|
|
||||||
|
/** @extends Node<'ConstTag'> */
|
||||||
|
export default class ConstTag extends Node {
|
||||||
|
/** @type {import('./shared/Expression.js').default} */
|
||||||
|
expression;
|
||||||
|
|
||||||
|
/** @type {import('./shared/Context.js').Context[]} */
|
||||||
|
contexts = [];
|
||||||
|
|
||||||
|
/** @type {import('../../interfaces.js').ConstTag} */
|
||||||
|
node;
|
||||||
|
|
||||||
|
/** @type {import('./shared/TemplateScope.js').default} */
|
||||||
|
scope;
|
||||||
|
|
||||||
|
/** @type {Map<string, import('estree').Node>} */
|
||||||
|
context_rest_properties = new Map();
|
||||||
|
|
||||||
|
/** @type {Set<string>} */
|
||||||
|
assignees = new Set();
|
||||||
|
|
||||||
|
/** @type {Set<string>} */
|
||||||
|
dependencies = new Set();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('../Component.js').default} component
|
||||||
|
* @param {import('./interfaces.js').INodeAllowConstTag} parent
|
||||||
|
* @param {import('./shared/TemplateScope.js').default} scope
|
||||||
|
* @param {import('../../interfaces.js').ConstTag} info
|
||||||
|
*/
|
||||||
|
constructor(component, parent, scope, info) {
|
||||||
|
super(component, parent, scope, info);
|
||||||
|
if (!allowed_parents.has(parent.type)) {
|
||||||
|
component.error(info, compiler_errors.invalid_const_placement);
|
||||||
|
}
|
||||||
|
this.node = info;
|
||||||
|
this.scope = scope;
|
||||||
|
const { assignees, dependencies } = this;
|
||||||
|
extract_identifiers(info.expression.left).forEach(
|
||||||
|
/** @param {any}params_0 */ ({ name }) => {
|
||||||
|
assignees.add(name);
|
||||||
|
const owner = this.scope.get_owner(name);
|
||||||
|
if (owner === parent) {
|
||||||
|
component.error(info, compiler_errors.invalid_const_declaration(name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
walk(info.expression.right, {
|
||||||
|
/**
|
||||||
|
* @param {any} node
|
||||||
|
* @param {any} parent
|
||||||
|
*/
|
||||||
|
enter(node, parent) {
|
||||||
|
if (
|
||||||
|
is_reference(
|
||||||
|
/** @type {import('is-reference').NodeWithPropertyDefinition} */ (node),
|
||||||
|
/** @type {import('is-reference').NodeWithPropertyDefinition} */ (parent)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
const identifier = get_object(/** @type {any} */ (node));
|
||||||
|
const { name } = identifier;
|
||||||
|
dependencies.add(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_expression() {
|
||||||
|
unpack_destructuring({
|
||||||
|
contexts: this.contexts,
|
||||||
|
node: this.node.expression.left,
|
||||||
|
scope: this.scope,
|
||||||
|
component: this.component,
|
||||||
|
context_rest_properties: this.context_rest_properties
|
||||||
|
});
|
||||||
|
this.expression = new Expression(this.component, this, this.scope, this.node.expression.right);
|
||||||
|
this.contexts.forEach(
|
||||||
|
/** @param {any} context */ (context) => {
|
||||||
|
if (context.type !== 'DestructuredVariable') return;
|
||||||
|
const owner = this.scope.get_owner(context.key.name);
|
||||||
|
if (owner && owner.type === 'ConstTag' && owner.parent === this.parent) {
|
||||||
|
this.component.error(
|
||||||
|
this.node,
|
||||||
|
compiler_errors.invalid_const_declaration(context.key.name)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this.scope.add(context.key.name, this.expression.dependencies, this);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,94 +0,0 @@
|
|||||||
import Node from './shared/Node';
|
|
||||||
import Expression from './shared/Expression';
|
|
||||||
import Component from '../Component';
|
|
||||||
import TemplateScope from './shared/TemplateScope';
|
|
||||||
import { Context, unpack_destructuring } from './shared/Context';
|
|
||||||
import { ConstTag as ConstTagType } from '../../interfaces';
|
|
||||||
import { INodeAllowConstTag } from './interfaces';
|
|
||||||
import { walk } from 'estree-walker';
|
|
||||||
import { extract_identifiers } from 'periscopic';
|
|
||||||
import is_reference, { NodeWithPropertyDefinition } from 'is-reference';
|
|
||||||
import get_object from '../utils/get_object';
|
|
||||||
import compiler_errors from '../compiler_errors';
|
|
||||||
import { Node as ESTreeNode } from 'estree';
|
|
||||||
|
|
||||||
const allowed_parents = new Set([
|
|
||||||
'EachBlock',
|
|
||||||
'CatchBlock',
|
|
||||||
'ThenBlock',
|
|
||||||
'InlineComponent',
|
|
||||||
'SlotTemplate',
|
|
||||||
'IfBlock',
|
|
||||||
'ElseBlock'
|
|
||||||
]);
|
|
||||||
|
|
||||||
export default class ConstTag extends Node {
|
|
||||||
type: 'ConstTag';
|
|
||||||
expression: Expression;
|
|
||||||
contexts: Context[] = [];
|
|
||||||
node: ConstTagType;
|
|
||||||
scope: TemplateScope;
|
|
||||||
context_rest_properties: Map<string, ESTreeNode> = new Map();
|
|
||||||
|
|
||||||
assignees: Set<string> = new Set();
|
|
||||||
dependencies: Set<string> = new Set();
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
component: Component,
|
|
||||||
parent: INodeAllowConstTag,
|
|
||||||
scope: TemplateScope,
|
|
||||||
info: ConstTagType
|
|
||||||
) {
|
|
||||||
super(component, parent, scope, info);
|
|
||||||
|
|
||||||
if (!allowed_parents.has(parent.type)) {
|
|
||||||
component.error(info, compiler_errors.invalid_const_placement);
|
|
||||||
}
|
|
||||||
this.node = info;
|
|
||||||
this.scope = scope;
|
|
||||||
|
|
||||||
const { assignees, dependencies } = this;
|
|
||||||
|
|
||||||
extract_identifiers(info.expression.left).forEach(({ name }) => {
|
|
||||||
assignees.add(name);
|
|
||||||
const owner = this.scope.get_owner(name);
|
|
||||||
if (owner === parent) {
|
|
||||||
component.error(info, compiler_errors.invalid_const_declaration(name));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
walk(info.expression.right, {
|
|
||||||
enter(node, parent) {
|
|
||||||
if (
|
|
||||||
is_reference(node as NodeWithPropertyDefinition, parent as NodeWithPropertyDefinition)
|
|
||||||
) {
|
|
||||||
const identifier = get_object(node as any);
|
|
||||||
const { name } = identifier;
|
|
||||||
dependencies.add(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
parse_expression() {
|
|
||||||
unpack_destructuring({
|
|
||||||
contexts: this.contexts,
|
|
||||||
node: this.node.expression.left,
|
|
||||||
scope: this.scope,
|
|
||||||
component: this.component,
|
|
||||||
context_rest_properties: this.context_rest_properties
|
|
||||||
});
|
|
||||||
this.expression = new Expression(this.component, this, this.scope, this.node.expression.right);
|
|
||||||
this.contexts.forEach((context) => {
|
|
||||||
if (context.type !== 'DestructuredVariable') return;
|
|
||||||
const owner = this.scope.get_owner(context.key.name);
|
|
||||||
if (owner && owner.type === 'ConstTag' && owner.parent === this.parent) {
|
|
||||||
this.component.error(
|
|
||||||
this.node,
|
|
||||||
compiler_errors.invalid_const_declaration(context.key.name)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
this.scope.add(context.key.name, this.expression.dependencies, this);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,23 @@
|
|||||||
|
import Node from './shared/Node.js';
|
||||||
|
import Expression from './shared/Expression.js';
|
||||||
|
|
||||||
|
/** @extends Node<'DebugTag'> */
|
||||||
|
export default class DebugTag extends Node {
|
||||||
|
/** @type {import('./shared/Expression.js').default[]} */
|
||||||
|
expressions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('../Component.js').default} component
|
||||||
|
* @param {import('./interfaces.js').INode} parent
|
||||||
|
* @param {import('./shared/TemplateScope.js').default} scope
|
||||||
|
* @param {import('../../interfaces.js').TemplateNode} info
|
||||||
|
*/
|
||||||
|
constructor(component, parent, scope, info) {
|
||||||
|
super(component, parent, scope, info);
|
||||||
|
this.expressions = info.identifiers.map(
|
||||||
|
/** @param {import('estree').Node} node */ (node) => {
|
||||||
|
return new Expression(component, parent, scope, node);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,20 +0,0 @@
|
|||||||
import Node from './shared/Node';
|
|
||||||
import Expression from './shared/Expression';
|
|
||||||
import Component from '../Component';
|
|
||||||
import TemplateScope from './shared/TemplateScope';
|
|
||||||
import { TemplateNode } from '../../interfaces';
|
|
||||||
import { INode } from './interfaces';
|
|
||||||
import { Node as EsTreeNode } from 'estree';
|
|
||||||
|
|
||||||
export default class DebugTag extends Node {
|
|
||||||
type: 'DebugTag';
|
|
||||||
expressions: Expression[];
|
|
||||||
|
|
||||||
constructor(component: Component, parent: INode, scope: TemplateScope, info: TemplateNode) {
|
|
||||||
super(component, parent, scope, info);
|
|
||||||
|
|
||||||
this.expressions = info.identifiers.map((node: EsTreeNode) => {
|
|
||||||
return new Expression(component, parent, scope, node);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,77 @@
|
|||||||
|
import Node from './shared/Node.js';
|
||||||
|
import Binding from './Binding.js';
|
||||||
|
import EventHandler from './EventHandler.js';
|
||||||
|
import fuzzymatch from '../../utils/fuzzymatch.js';
|
||||||
|
import Action from './Action.js';
|
||||||
|
import list from '../../utils/list.js';
|
||||||
|
import compiler_warnings from '../compiler_warnings.js';
|
||||||
|
import compiler_errors from '../compiler_errors.js';
|
||||||
|
|
||||||
|
const valid_bindings = ['fullscreenElement', 'visibilityState'];
|
||||||
|
|
||||||
|
/** @extends Node<'Document'> */
|
||||||
|
export default class Document extends Node {
|
||||||
|
/** @type {import('./EventHandler.js').default[]} */
|
||||||
|
handlers = [];
|
||||||
|
|
||||||
|
/** @type {import('./Binding.js').default[]} */
|
||||||
|
bindings = [];
|
||||||
|
|
||||||
|
/** @type {import('./Action.js').default[]} */
|
||||||
|
actions = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('../Component.js').default} component
|
||||||
|
* @param {import('./shared/Node.js').default} parent
|
||||||
|
* @param {import('./shared/TemplateScope.js').default} scope
|
||||||
|
* @param {import('../../interfaces.js').Element} info
|
||||||
|
*/
|
||||||
|
constructor(component, parent, scope, info) {
|
||||||
|
super(component, parent, scope, info);
|
||||||
|
info.attributes.forEach(
|
||||||
|
/** @param {any} node */ (node) => {
|
||||||
|
if (node.type === 'EventHandler') {
|
||||||
|
this.handlers.push(new EventHandler(component, this, scope, node));
|
||||||
|
} else if (node.type === 'Binding') {
|
||||||
|
if (!~valid_bindings.indexOf(node.name)) {
|
||||||
|
const match = fuzzymatch(node.name, valid_bindings);
|
||||||
|
if (match) {
|
||||||
|
return component.error(
|
||||||
|
node,
|
||||||
|
compiler_errors.invalid_binding_on(
|
||||||
|
node.name,
|
||||||
|
'<svelte:document>',
|
||||||
|
` (did you mean '${match}'?)`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return component.error(
|
||||||
|
node,
|
||||||
|
compiler_errors.invalid_binding_on(
|
||||||
|
node.name,
|
||||||
|
'<svelte:document>',
|
||||||
|
` — valid bindings are ${list(valid_bindings)}`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.bindings.push(new Binding(component, this, scope, node));
|
||||||
|
} else if (node.type === 'Action') {
|
||||||
|
this.actions.push(new Action(component, this, scope, node));
|
||||||
|
} else {
|
||||||
|
// TODO there shouldn't be anything else here...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
this.validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @private */
|
||||||
|
validate() {
|
||||||
|
const handlers_map = new Set();
|
||||||
|
this.handlers.forEach(/** @param {any} handler */ (handler) => handlers_map.add(handler.name));
|
||||||
|
if (handlers_map.has('mouseenter') || handlers_map.has('mouseleave')) {
|
||||||
|
this.component.warn(this, compiler_warnings.avoid_mouse_events_on_document);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,71 +0,0 @@
|
|||||||
import Node from './shared/Node';
|
|
||||||
import Binding from './Binding';
|
|
||||||
import EventHandler from './EventHandler';
|
|
||||||
import fuzzymatch from '../../utils/fuzzymatch';
|
|
||||||
import Action from './Action';
|
|
||||||
import Component from '../Component';
|
|
||||||
import list from '../../utils/list';
|
|
||||||
import TemplateScope from './shared/TemplateScope';
|
|
||||||
import { Element } from '../../interfaces';
|
|
||||||
import compiler_warnings from '../compiler_warnings';
|
|
||||||
import compiler_errors from '../compiler_errors';
|
|
||||||
|
|
||||||
const valid_bindings = ['fullscreenElement', 'visibilityState'];
|
|
||||||
|
|
||||||
export default class Document extends Node {
|
|
||||||
type: 'Document';
|
|
||||||
handlers: EventHandler[] = [];
|
|
||||||
bindings: Binding[] = [];
|
|
||||||
actions: Action[] = [];
|
|
||||||
|
|
||||||
constructor(component: Component, parent: Node, scope: TemplateScope, info: Element) {
|
|
||||||
super(component, parent, scope, info);
|
|
||||||
|
|
||||||
info.attributes.forEach((node) => {
|
|
||||||
if (node.type === 'EventHandler') {
|
|
||||||
this.handlers.push(new EventHandler(component, this, scope, node));
|
|
||||||
} else if (node.type === 'Binding') {
|
|
||||||
if (!~valid_bindings.indexOf(node.name)) {
|
|
||||||
const match = fuzzymatch(node.name, valid_bindings);
|
|
||||||
if (match) {
|
|
||||||
return component.error(
|
|
||||||
node,
|
|
||||||
compiler_errors.invalid_binding_on(
|
|
||||||
node.name,
|
|
||||||
'<svelte:document>',
|
|
||||||
` (did you mean '${match}'?)`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return component.error(
|
|
||||||
node,
|
|
||||||
compiler_errors.invalid_binding_on(
|
|
||||||
node.name,
|
|
||||||
'<svelte:document>',
|
|
||||||
` — valid bindings are ${list(valid_bindings)}`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.bindings.push(new Binding(component, this, scope, node));
|
|
||||||
} else if (node.type === 'Action') {
|
|
||||||
this.actions.push(new Action(component, this, scope, node));
|
|
||||||
} else {
|
|
||||||
// TODO there shouldn't be anything else here...
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.validate();
|
|
||||||
}
|
|
||||||
|
|
||||||
private validate() {
|
|
||||||
const handlers_map = new Set();
|
|
||||||
|
|
||||||
this.handlers.forEach((handler) => handlers_map.add(handler.name));
|
|
||||||
|
|
||||||
if (handlers_map.has('mouseenter') || handlers_map.has('mouseleave')) {
|
|
||||||
this.component.warn(this, compiler_warnings.avoid_mouse_events_on_document);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,117 @@
|
|||||||
|
import ElseBlock from './ElseBlock.js';
|
||||||
|
import Expression from './shared/Expression.js';
|
||||||
|
import AbstractBlock from './shared/AbstractBlock.js';
|
||||||
|
import { unpack_destructuring } from './shared/Context.js';
|
||||||
|
import compiler_errors from '../compiler_errors.js';
|
||||||
|
import get_const_tags from './shared/get_const_tags.js';
|
||||||
|
|
||||||
|
/** @extends AbstractBlock<'EachBlock'> */
|
||||||
|
export default class EachBlock extends AbstractBlock {
|
||||||
|
/** @type {import('./shared/Expression.js').default} */
|
||||||
|
expression;
|
||||||
|
|
||||||
|
/** @type {import('estree').Node} */
|
||||||
|
context_node;
|
||||||
|
|
||||||
|
/** @type {string} */
|
||||||
|
iterations;
|
||||||
|
|
||||||
|
/** @type {string} */
|
||||||
|
index;
|
||||||
|
|
||||||
|
/** @type {string} */
|
||||||
|
context;
|
||||||
|
|
||||||
|
/** @type {import('./shared/Expression.js').default} */
|
||||||
|
key;
|
||||||
|
|
||||||
|
/** @type {import('./shared/TemplateScope.js').default} */
|
||||||
|
scope;
|
||||||
|
|
||||||
|
/** @type {import('./shared/Context.js').Context[]} */
|
||||||
|
contexts;
|
||||||
|
|
||||||
|
/** @type {import('./ConstTag.js').default[]} */
|
||||||
|
const_tags;
|
||||||
|
|
||||||
|
/** @type {boolean} */
|
||||||
|
has_animation;
|
||||||
|
/** */
|
||||||
|
has_binding = false;
|
||||||
|
/** */
|
||||||
|
has_index_binding = false;
|
||||||
|
|
||||||
|
/** @type {Map<string, import('estree').Node>} */
|
||||||
|
context_rest_properties;
|
||||||
|
|
||||||
|
/** @type {import('./ElseBlock.js').default} */
|
||||||
|
else;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('../Component.js').default} component
|
||||||
|
* @param {import('estree').Node} parent
|
||||||
|
* @param {import('./shared/TemplateScope.js').default} scope
|
||||||
|
* @param {import('../../interfaces.js').TemplateNode} info
|
||||||
|
*/
|
||||||
|
constructor(component, parent, scope, info) {
|
||||||
|
super(component, parent, scope, info);
|
||||||
|
this.cannot_use_innerhtml();
|
||||||
|
this.not_static_content();
|
||||||
|
this.expression = new Expression(component, this, scope, info.expression);
|
||||||
|
this.context = info.context.name || 'each'; // TODO this is used to facilitate binding; currently fails with destructuring
|
||||||
|
this.context_node = info.context;
|
||||||
|
this.index = info.index;
|
||||||
|
this.scope = scope.child();
|
||||||
|
this.context_rest_properties = new Map();
|
||||||
|
this.contexts = [];
|
||||||
|
unpack_destructuring({
|
||||||
|
contexts: this.contexts,
|
||||||
|
node: info.context,
|
||||||
|
scope,
|
||||||
|
component,
|
||||||
|
context_rest_properties: this.context_rest_properties
|
||||||
|
});
|
||||||
|
this.contexts.forEach(
|
||||||
|
/** @param {any} context */ (context) => {
|
||||||
|
if (context.type !== 'DestructuredVariable') return;
|
||||||
|
this.scope.add(context.key.name, this.expression.dependencies, this);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (this.index) {
|
||||||
|
// index can only change if this is a keyed each block
|
||||||
|
const dependencies = info.key ? this.expression.dependencies : new Set([]);
|
||||||
|
this.scope.add(this.index, dependencies, this);
|
||||||
|
}
|
||||||
|
this.key = info.key ? new Expression(component, this, this.scope, info.key) : null;
|
||||||
|
this.has_animation = false;
|
||||||
|
[this.const_tags, this.children] = get_const_tags(info.children, component, this, this);
|
||||||
|
if (this.has_animation) {
|
||||||
|
this.children = this.children.filter(
|
||||||
|
/** @param {any} child */ (child) => !isEmptyNode(child) && !isCommentNode(child)
|
||||||
|
);
|
||||||
|
if (this.children.length !== 1) {
|
||||||
|
const child = this.children.find(
|
||||||
|
/** @param {any} child */ (child) =>
|
||||||
|
!!(/** @type {import('./Element.js').default} */ (child).animation)
|
||||||
|
);
|
||||||
|
component.error(
|
||||||
|
/** @type {import('./Element.js').default} */ (child).animation,
|
||||||
|
compiler_errors.invalid_animation_sole
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.warn_if_empty_block();
|
||||||
|
this.else = info.else ? new ElseBlock(component, this, this.scope, info.else) : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {import('./interfaces.js').INode} node */
|
||||||
|
function isEmptyNode(node) {
|
||||||
|
return node.type === 'Text' && node.data.trim() === '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {import('./interfaces.js').INode} node */
|
||||||
|
function isCommentNode(node) {
|
||||||
|
return node.type === 'Comment';
|
||||||
|
}
|
@ -1,93 +0,0 @@
|
|||||||
import ElseBlock from './ElseBlock';
|
|
||||||
import Expression from './shared/Expression';
|
|
||||||
import TemplateScope from './shared/TemplateScope';
|
|
||||||
import AbstractBlock from './shared/AbstractBlock';
|
|
||||||
import Element from './Element';
|
|
||||||
import ConstTag from './ConstTag';
|
|
||||||
import { Context, unpack_destructuring } from './shared/Context';
|
|
||||||
import { Node } from 'estree';
|
|
||||||
import Component from '../Component';
|
|
||||||
import { TemplateNode } from '../../interfaces';
|
|
||||||
import compiler_errors from '../compiler_errors';
|
|
||||||
import { INode } from './interfaces';
|
|
||||||
import get_const_tags from './shared/get_const_tags';
|
|
||||||
|
|
||||||
export default class EachBlock extends AbstractBlock {
|
|
||||||
type: 'EachBlock';
|
|
||||||
|
|
||||||
expression: Expression;
|
|
||||||
context_node: Node;
|
|
||||||
|
|
||||||
iterations: string;
|
|
||||||
index: string;
|
|
||||||
context: string;
|
|
||||||
key: Expression;
|
|
||||||
scope: TemplateScope;
|
|
||||||
contexts: Context[];
|
|
||||||
const_tags: ConstTag[];
|
|
||||||
has_animation: boolean;
|
|
||||||
has_binding = false;
|
|
||||||
has_index_binding = false;
|
|
||||||
context_rest_properties: Map<string, Node>;
|
|
||||||
else?: ElseBlock;
|
|
||||||
|
|
||||||
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
|
|
||||||
super(component, parent, scope, info);
|
|
||||||
this.cannot_use_innerhtml();
|
|
||||||
this.not_static_content();
|
|
||||||
|
|
||||||
this.expression = new Expression(component, this, scope, info.expression);
|
|
||||||
this.context = info.context.name || 'each'; // TODO this is used to facilitate binding; currently fails with destructuring
|
|
||||||
this.context_node = info.context;
|
|
||||||
this.index = info.index;
|
|
||||||
|
|
||||||
this.scope = scope.child();
|
|
||||||
this.context_rest_properties = new Map();
|
|
||||||
this.contexts = [];
|
|
||||||
unpack_destructuring({
|
|
||||||
contexts: this.contexts,
|
|
||||||
node: info.context,
|
|
||||||
scope,
|
|
||||||
component,
|
|
||||||
context_rest_properties: this.context_rest_properties
|
|
||||||
});
|
|
||||||
|
|
||||||
this.contexts.forEach((context) => {
|
|
||||||
if (context.type !== 'DestructuredVariable') return;
|
|
||||||
this.scope.add(context.key.name, this.expression.dependencies, this);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.index) {
|
|
||||||
// index can only change if this is a keyed each block
|
|
||||||
const dependencies = info.key ? this.expression.dependencies : new Set([]);
|
|
||||||
this.scope.add(this.index, dependencies, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.key = info.key ? new Expression(component, this, this.scope, info.key) : null;
|
|
||||||
|
|
||||||
this.has_animation = false;
|
|
||||||
|
|
||||||
[this.const_tags, this.children] = get_const_tags(info.children, component, this, this);
|
|
||||||
|
|
||||||
if (this.has_animation) {
|
|
||||||
this.children = this.children.filter((child) => !isEmptyNode(child) && !isCommentNode(child));
|
|
||||||
|
|
||||||
if (this.children.length !== 1) {
|
|
||||||
const child = this.children.find((child) => !!(child as Element).animation);
|
|
||||||
component.error((child as Element).animation, compiler_errors.invalid_animation_sole);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.warn_if_empty_block();
|
|
||||||
|
|
||||||
this.else = info.else ? new ElseBlock(component, this, this.scope, info.else) : null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function isEmptyNode(node: INode) {
|
|
||||||
return node.type === 'Text' && node.data.trim() === '';
|
|
||||||
}
|
|
||||||
function isCommentNode(node: INode) {
|
|
||||||
return node.type === 'Comment';
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,24 @@
|
|||||||
|
import AbstractBlock from './shared/AbstractBlock.js';
|
||||||
|
import get_const_tags from './shared/get_const_tags.js';
|
||||||
|
|
||||||
|
/** @extends AbstractBlock<'ElseBlock'> */
|
||||||
|
export default class ElseBlock extends AbstractBlock {
|
||||||
|
/** @type {import('./shared/TemplateScope.js').default} */
|
||||||
|
scope;
|
||||||
|
|
||||||
|
/** @type {import('./ConstTag.js').default[]} */
|
||||||
|
const_tags;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('../Component.js').default} component
|
||||||
|
* @param {import('./shared/Node.js').default} parent
|
||||||
|
* @param {import('./shared/TemplateScope.js').default} scope
|
||||||
|
* @param {import('../../interfaces.js').TemplateNode} info
|
||||||
|
*/
|
||||||
|
constructor(component, parent, scope, info) {
|
||||||
|
super(component, parent, scope, info);
|
||||||
|
this.scope = scope.child();
|
||||||
|
[this.const_tags, this.children] = get_const_tags(info.children, component, this, this);
|
||||||
|
this.warn_if_empty_block();
|
||||||
|
}
|
||||||
|
}
|
@ -1,22 +0,0 @@
|
|||||||
import AbstractBlock from './shared/AbstractBlock';
|
|
||||||
import Component from '../Component';
|
|
||||||
import TemplateScope from './shared/TemplateScope';
|
|
||||||
import { TemplateNode } from '../../interfaces';
|
|
||||||
import Node from './shared/Node';
|
|
||||||
import ConstTag from './ConstTag';
|
|
||||||
import get_const_tags from './shared/get_const_tags';
|
|
||||||
|
|
||||||
export default class ElseBlock extends AbstractBlock {
|
|
||||||
type: 'ElseBlock';
|
|
||||||
scope: TemplateScope;
|
|
||||||
const_tags: ConstTag[];
|
|
||||||
|
|
||||||
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
|
|
||||||
super(component, parent, scope, info);
|
|
||||||
|
|
||||||
this.scope = scope.child();
|
|
||||||
[this.const_tags, this.children] = get_const_tags(info.children, component, this, this);
|
|
||||||
|
|
||||||
this.warn_if_empty_block();
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,26 @@
|
|||||||
|
import Node from './shared/Node.js';
|
||||||
|
import map_children from './shared/map_children.js';
|
||||||
|
import TemplateScope from './shared/TemplateScope.js';
|
||||||
|
|
||||||
|
/** @extends Node<'Fragment'> */
|
||||||
|
export default class Fragment extends Node {
|
||||||
|
/** @type {import('../render_dom/Block.js').default} */
|
||||||
|
block;
|
||||||
|
|
||||||
|
/** @type {import('./interfaces.js').INode[]} */
|
||||||
|
children;
|
||||||
|
|
||||||
|
/** @type {import('./shared/TemplateScope.js').default} */
|
||||||
|
scope;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('../Component.js').default} component
|
||||||
|
* @param {import('../../interfaces.js').TemplateNode} info
|
||||||
|
*/
|
||||||
|
constructor(component, info) {
|
||||||
|
const scope = new TemplateScope();
|
||||||
|
super(component, null, scope, info);
|
||||||
|
this.scope = scope;
|
||||||
|
this.children = map_children(component, this, scope, info.children);
|
||||||
|
}
|
||||||
|
}
|
@ -1,22 +0,0 @@
|
|||||||
import Node from './shared/Node';
|
|
||||||
import Component from '../Component';
|
|
||||||
import map_children from './shared/map_children';
|
|
||||||
import Block from '../render_dom/Block';
|
|
||||||
import TemplateScope from './shared/TemplateScope';
|
|
||||||
import { INode } from './interfaces';
|
|
||||||
import { TemplateNode } from '../../interfaces';
|
|
||||||
|
|
||||||
export default class Fragment extends Node {
|
|
||||||
type: 'Fragment';
|
|
||||||
block: Block;
|
|
||||||
children: INode[];
|
|
||||||
scope: TemplateScope;
|
|
||||||
|
|
||||||
constructor(component: Component, info: TemplateNode) {
|
|
||||||
const scope = new TemplateScope();
|
|
||||||
super(component, null, scope, info);
|
|
||||||
|
|
||||||
this.scope = scope;
|
|
||||||
this.children = map_children(component, this, scope, info.children);
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,42 @@
|
|||||||
|
import Node from './shared/Node.js';
|
||||||
|
import map_children from './shared/map_children.js';
|
||||||
|
import hash from '../utils/hash.js';
|
||||||
|
import compiler_errors from '../compiler_errors.js';
|
||||||
|
import { regex_non_whitespace_character } from '../../utils/patterns.js';
|
||||||
|
|
||||||
|
/** @extends Node<'Head'> */
|
||||||
|
export default class Head extends Node {
|
||||||
|
/** @type {any[]} */
|
||||||
|
children; // TODO
|
||||||
|
|
||||||
|
/** @type {string} */
|
||||||
|
id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('../Component.js').default} component
|
||||||
|
* @param {import('./shared/Node.js').default} parent
|
||||||
|
* @param {import('./shared/TemplateScope.js').default} scope
|
||||||
|
* @param {import('../../interfaces.js').TemplateNode} info
|
||||||
|
*/
|
||||||
|
constructor(component, parent, scope, info) {
|
||||||
|
super(component, parent, scope, info);
|
||||||
|
this.cannot_use_innerhtml();
|
||||||
|
if (info.attributes.length) {
|
||||||
|
component.error(info.attributes[0], compiler_errors.invalid_attribute_head);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.children = map_children(
|
||||||
|
component,
|
||||||
|
parent,
|
||||||
|
scope,
|
||||||
|
info.children.filter(
|
||||||
|
/** @param {any} child */ (child) => {
|
||||||
|
return child.type !== 'Text' || regex_non_whitespace_character.test(child.data);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if (this.children.length > 0) {
|
||||||
|
this.id = `svelte-${hash(this.component.source.slice(this.start, this.end))}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,38 +0,0 @@
|
|||||||
import Node from './shared/Node';
|
|
||||||
import map_children from './shared/map_children';
|
|
||||||
import hash from '../utils/hash';
|
|
||||||
import Component from '../Component';
|
|
||||||
import TemplateScope from './shared/TemplateScope';
|
|
||||||
import { TemplateNode } from '../../interfaces';
|
|
||||||
import compiler_errors from '../compiler_errors';
|
|
||||||
import { regex_non_whitespace_character } from '../../utils/patterns';
|
|
||||||
|
|
||||||
export default class Head extends Node {
|
|
||||||
type: 'Head';
|
|
||||||
children: any[]; // TODO
|
|
||||||
id: string;
|
|
||||||
|
|
||||||
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
|
|
||||||
super(component, parent, scope, info);
|
|
||||||
|
|
||||||
this.cannot_use_innerhtml();
|
|
||||||
|
|
||||||
if (info.attributes.length) {
|
|
||||||
component.error(info.attributes[0], compiler_errors.invalid_attribute_head);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.children = map_children(
|
|
||||||
component,
|
|
||||||
parent,
|
|
||||||
scope,
|
|
||||||
info.children.filter((child) => {
|
|
||||||
return child.type !== 'Text' || regex_non_whitespace_character.test(child.data);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
if (this.children.length > 0) {
|
|
||||||
this.id = `svelte-${hash(this.component.source.slice(this.start, this.end))}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,36 @@
|
|||||||
|
import ElseBlock from './ElseBlock.js';
|
||||||
|
import Expression from './shared/Expression.js';
|
||||||
|
import AbstractBlock from './shared/AbstractBlock.js';
|
||||||
|
import get_const_tags from './shared/get_const_tags.js';
|
||||||
|
|
||||||
|
/** @extends AbstractBlock<'IfBlock'> */
|
||||||
|
export default class IfBlock extends AbstractBlock {
|
||||||
|
/** @type {import('./shared/Expression.js').default} */
|
||||||
|
expression;
|
||||||
|
|
||||||
|
/** @type {import('./ElseBlock.js').default} */
|
||||||
|
else;
|
||||||
|
|
||||||
|
/** @type {import('./shared/TemplateScope.js').default} */
|
||||||
|
scope;
|
||||||
|
|
||||||
|
/** @type {import('./ConstTag.js').default[]} */
|
||||||
|
const_tags;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('../Component.js').default} component
|
||||||
|
* @param {import('./shared/Node.js').default} parent
|
||||||
|
* @param {import('./shared/TemplateScope.js').default} scope
|
||||||
|
* @param {import('../../interfaces.js').TemplateNode} info
|
||||||
|
*/
|
||||||
|
constructor(component, parent, scope, info) {
|
||||||
|
super(component, parent, scope, info);
|
||||||
|
this.scope = scope.child();
|
||||||
|
this.cannot_use_innerhtml();
|
||||||
|
this.not_static_content();
|
||||||
|
this.expression = new Expression(component, this, this.scope, info.expression);
|
||||||
|
[this.const_tags, this.children] = get_const_tags(info.children, component, this, this);
|
||||||
|
this.else = info.else ? new ElseBlock(component, this, scope, info.else) : null;
|
||||||
|
this.warn_if_empty_block();
|
||||||
|
}
|
||||||
|
}
|
@ -1,31 +0,0 @@
|
|||||||
import ElseBlock from './ElseBlock';
|
|
||||||
import Expression from './shared/Expression';
|
|
||||||
import AbstractBlock from './shared/AbstractBlock';
|
|
||||||
import Component from '../Component';
|
|
||||||
import TemplateScope from './shared/TemplateScope';
|
|
||||||
import { TemplateNode } from '../../interfaces';
|
|
||||||
import Node from './shared/Node';
|
|
||||||
import ConstTag from './ConstTag';
|
|
||||||
import get_const_tags from './shared/get_const_tags';
|
|
||||||
|
|
||||||
export default class IfBlock extends AbstractBlock {
|
|
||||||
type: 'IfBlock';
|
|
||||||
expression: Expression;
|
|
||||||
else: ElseBlock;
|
|
||||||
scope: TemplateScope;
|
|
||||||
const_tags: ConstTag[];
|
|
||||||
|
|
||||||
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
|
|
||||||
super(component, parent, scope, info);
|
|
||||||
this.scope = scope.child();
|
|
||||||
this.cannot_use_innerhtml();
|
|
||||||
this.not_static_content();
|
|
||||||
|
|
||||||
this.expression = new Expression(component, this, this.scope, info.expression);
|
|
||||||
[this.const_tags, this.children] = get_const_tags(info.children, component, this, this);
|
|
||||||
|
|
||||||
this.else = info.else ? new ElseBlock(component, this, scope, info.else) : null;
|
|
||||||
|
|
||||||
this.warn_if_empty_block();
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,203 @@
|
|||||||
|
import Node from './shared/Node.js';
|
||||||
|
import Attribute from './Attribute.js';
|
||||||
|
import map_children from './shared/map_children.js';
|
||||||
|
import Binding from './Binding.js';
|
||||||
|
import EventHandler from './EventHandler.js';
|
||||||
|
import Expression from './shared/Expression.js';
|
||||||
|
import Let from './Let.js';
|
||||||
|
import compiler_errors from '../compiler_errors.js';
|
||||||
|
import { regex_only_whitespaces } from '../../utils/patterns.js';
|
||||||
|
|
||||||
|
/** @extends Node<'InlineComponent'> */
|
||||||
|
export default class InlineComponent extends Node {
|
||||||
|
/** @type {string} */
|
||||||
|
name;
|
||||||
|
|
||||||
|
/** @type {import('./shared/Expression.js').default} */
|
||||||
|
expression;
|
||||||
|
|
||||||
|
/** @type {import('./Binding.js').default[]} */
|
||||||
|
bindings = [];
|
||||||
|
|
||||||
|
/** @type {import('./EventHandler.js').default[]} */
|
||||||
|
handlers = [];
|
||||||
|
|
||||||
|
/** @type {import('./Let.js').default[]} */
|
||||||
|
lets = [];
|
||||||
|
|
||||||
|
/** @type {import('./Attribute.js').default[]} */
|
||||||
|
css_custom_properties = [];
|
||||||
|
|
||||||
|
/** @type {import('./interfaces.js').INode[]} */
|
||||||
|
children;
|
||||||
|
|
||||||
|
/** @type {import('./shared/TemplateScope.js').default} */
|
||||||
|
scope;
|
||||||
|
|
||||||
|
/** @type {string} */
|
||||||
|
namespace;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('../Component.js').default} component
|
||||||
|
* @param {import('./shared/Node.js').default} parent
|
||||||
|
* @param {import('./shared/TemplateScope.js').default} scope
|
||||||
|
* @param {import('../../interfaces.js').TemplateNode} info
|
||||||
|
*/
|
||||||
|
constructor(component, parent, scope, info) {
|
||||||
|
super(component, parent, scope, info);
|
||||||
|
this.cannot_use_innerhtml();
|
||||||
|
this.not_static_content();
|
||||||
|
if (info.name !== 'svelte:component' && info.name !== 'svelte:self') {
|
||||||
|
const name = info.name.split('.')[0]; // accommodate namespaces
|
||||||
|
component.warn_if_undefined(name, info, scope);
|
||||||
|
component.add_reference(/** @type {any} */ (this), name);
|
||||||
|
}
|
||||||
|
this.name = info.name;
|
||||||
|
this.namespace = get_namespace(parent, component.namespace);
|
||||||
|
this.expression =
|
||||||
|
this.name === 'svelte:component'
|
||||||
|
? new Expression(component, this, scope, info.expression)
|
||||||
|
: null;
|
||||||
|
info.attributes.forEach(
|
||||||
|
/** @param {any} node */ (node) => {
|
||||||
|
/* eslint-disable no-fallthrough */
|
||||||
|
switch (node.type) {
|
||||||
|
case 'Action':
|
||||||
|
return component.error(node, compiler_errors.invalid_action);
|
||||||
|
case 'Attribute':
|
||||||
|
if (node.name.startsWith('--')) {
|
||||||
|
this.css_custom_properties.push(new Attribute(component, this, scope, node));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// fallthrough
|
||||||
|
case 'Spread':
|
||||||
|
this.attributes.push(new Attribute(component, this, scope, node));
|
||||||
|
break;
|
||||||
|
case 'Binding':
|
||||||
|
this.bindings.push(new Binding(component, this, scope, node));
|
||||||
|
break;
|
||||||
|
case 'Class':
|
||||||
|
return component.error(node, compiler_errors.invalid_class);
|
||||||
|
case 'EventHandler':
|
||||||
|
this.handlers.push(new EventHandler(component, this, scope, node));
|
||||||
|
break;
|
||||||
|
case 'Let':
|
||||||
|
this.lets.push(new Let(component, this, scope, node));
|
||||||
|
break;
|
||||||
|
case 'Transition':
|
||||||
|
return component.error(node, compiler_errors.invalid_transition);
|
||||||
|
case 'StyleDirective':
|
||||||
|
return component.error(node, compiler_errors.invalid_component_style_directive);
|
||||||
|
default:
|
||||||
|
throw new Error(`Not implemented: ${node.type}`);
|
||||||
|
}
|
||||||
|
/* eslint-enable no-fallthrough */
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (this.lets.length > 0) {
|
||||||
|
this.scope = scope.child();
|
||||||
|
this.lets.forEach(
|
||||||
|
/** @param {any} l */ (l) => {
|
||||||
|
const dependencies = new Set([l.name.name]);
|
||||||
|
l.names.forEach(
|
||||||
|
/** @param {any} name */ (name) => {
|
||||||
|
this.scope.add(name, dependencies, this);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.scope = scope;
|
||||||
|
}
|
||||||
|
this.handlers.forEach(
|
||||||
|
/** @param {any} handler */ (handler) => {
|
||||||
|
handler.modifiers.forEach(
|
||||||
|
/** @param {any} modifier */ (modifier) => {
|
||||||
|
if (modifier !== 'once') {
|
||||||
|
return component.error(handler, compiler_errors.invalid_event_modifier_component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const children = [];
|
||||||
|
for (let i = info.children.length - 1; i >= 0; i--) {
|
||||||
|
const child = info.children[i];
|
||||||
|
if (child.type === 'SlotTemplate') {
|
||||||
|
children.push(child);
|
||||||
|
info.children.splice(i, 1);
|
||||||
|
} else if (
|
||||||
|
(child.type === 'Element' || child.type === 'InlineComponent' || child.type === 'Slot') &&
|
||||||
|
child.attributes.find(
|
||||||
|
/** @param {any} attribute */ (attribute) => attribute.name === 'slot'
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
const slot_template = {
|
||||||
|
start: child.start,
|
||||||
|
end: child.end,
|
||||||
|
type: 'SlotTemplate',
|
||||||
|
name: 'svelte:fragment',
|
||||||
|
attributes: [],
|
||||||
|
children: [child]
|
||||||
|
};
|
||||||
|
// transfer attributes
|
||||||
|
for (let i = child.attributes.length - 1; i >= 0; i--) {
|
||||||
|
const attribute = child.attributes[i];
|
||||||
|
if (attribute.type === 'Let') {
|
||||||
|
slot_template.attributes.push(attribute);
|
||||||
|
child.attributes.splice(i, 1);
|
||||||
|
} else if (attribute.type === 'Attribute' && attribute.name === 'slot') {
|
||||||
|
slot_template.attributes.push(attribute);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// transfer const
|
||||||
|
for (let i = child.children.length - 1; i >= 0; i--) {
|
||||||
|
const child_child = child.children[i];
|
||||||
|
if (child_child.type === 'ConstTag') {
|
||||||
|
slot_template.children.push(child_child);
|
||||||
|
child.children.splice(i, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
children.push(slot_template);
|
||||||
|
info.children.splice(i, 1);
|
||||||
|
} else if (child.type === 'Comment' && children.length > 0) {
|
||||||
|
children[children.length - 1].children.unshift(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (info.children.some(/** @param {any} node */ (node) => not_whitespace_text(node))) {
|
||||||
|
children.push({
|
||||||
|
start: info.start,
|
||||||
|
end: info.end,
|
||||||
|
type: 'SlotTemplate',
|
||||||
|
name: 'svelte:fragment',
|
||||||
|
attributes: [],
|
||||||
|
children: info.children
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.children = map_children(component, this, this.scope, children);
|
||||||
|
}
|
||||||
|
get slot_template_name() {
|
||||||
|
return /** @type {string} */ (
|
||||||
|
this.attributes
|
||||||
|
.find(/** @param {any} attribute */ (attribute) => attribute.name === 'slot')
|
||||||
|
.get_static_value()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {any} node */
|
||||||
|
function not_whitespace_text(node) {
|
||||||
|
return !(node.type === 'Text' && regex_only_whitespaces.test(node.data));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('./shared/Node.js').default} parent
|
||||||
|
* @param {string} explicit_namespace
|
||||||
|
*/
|
||||||
|
function get_namespace(parent, explicit_namespace) {
|
||||||
|
const parent_element = parent.find_nearest(/^Element/);
|
||||||
|
if (!parent_element) {
|
||||||
|
return explicit_namespace;
|
||||||
|
}
|
||||||
|
return parent_element.namespace;
|
||||||
|
}
|
@ -1,191 +0,0 @@
|
|||||||
import Node from './shared/Node';
|
|
||||||
import Attribute from './Attribute';
|
|
||||||
import map_children from './shared/map_children';
|
|
||||||
import Binding from './Binding';
|
|
||||||
import EventHandler from './EventHandler';
|
|
||||||
import Expression from './shared/Expression';
|
|
||||||
import Component from '../Component';
|
|
||||||
import Let from './Let';
|
|
||||||
import TemplateScope from './shared/TemplateScope';
|
|
||||||
import { INode } from './interfaces';
|
|
||||||
import { TemplateNode } from '../../interfaces';
|
|
||||||
import compiler_errors from '../compiler_errors';
|
|
||||||
import { regex_only_whitespaces } from '../../utils/patterns';
|
|
||||||
|
|
||||||
export default class InlineComponent extends Node {
|
|
||||||
type: 'InlineComponent';
|
|
||||||
name: string;
|
|
||||||
expression: Expression;
|
|
||||||
attributes: Attribute[] = [];
|
|
||||||
bindings: Binding[] = [];
|
|
||||||
handlers: EventHandler[] = [];
|
|
||||||
lets: Let[] = [];
|
|
||||||
css_custom_properties: Attribute[] = [];
|
|
||||||
children: INode[];
|
|
||||||
scope: TemplateScope;
|
|
||||||
namespace: string;
|
|
||||||
|
|
||||||
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
|
|
||||||
super(component, parent, scope, info);
|
|
||||||
|
|
||||||
this.cannot_use_innerhtml();
|
|
||||||
this.not_static_content();
|
|
||||||
|
|
||||||
if (info.name !== 'svelte:component' && info.name !== 'svelte:self') {
|
|
||||||
const name = info.name.split('.')[0]; // accommodate namespaces
|
|
||||||
component.warn_if_undefined(name, info, scope);
|
|
||||||
component.add_reference(this as any, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.name = info.name;
|
|
||||||
this.namespace = get_namespace(parent, component.namespace);
|
|
||||||
|
|
||||||
this.expression =
|
|
||||||
this.name === 'svelte:component'
|
|
||||||
? new Expression(component, this, scope, info.expression)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
info.attributes.forEach((node) => {
|
|
||||||
/* eslint-disable no-fallthrough */
|
|
||||||
switch (node.type) {
|
|
||||||
case 'Action':
|
|
||||||
return component.error(node, compiler_errors.invalid_action);
|
|
||||||
|
|
||||||
case 'Attribute':
|
|
||||||
if (node.name.startsWith('--')) {
|
|
||||||
this.css_custom_properties.push(new Attribute(component, this, scope, node));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// fallthrough
|
|
||||||
case 'Spread':
|
|
||||||
this.attributes.push(new Attribute(component, this, scope, node));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'Binding':
|
|
||||||
this.bindings.push(new Binding(component, this, scope, node));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'Class':
|
|
||||||
return component.error(node, compiler_errors.invalid_class);
|
|
||||||
|
|
||||||
case 'EventHandler':
|
|
||||||
this.handlers.push(new EventHandler(component, this, scope, node));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'Let':
|
|
||||||
this.lets.push(new Let(component, this, scope, node));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'Transition':
|
|
||||||
return component.error(node, compiler_errors.invalid_transition);
|
|
||||||
|
|
||||||
case 'StyleDirective':
|
|
||||||
return component.error(node, compiler_errors.invalid_component_style_directive);
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new Error(`Not implemented: ${node.type}`);
|
|
||||||
}
|
|
||||||
/* eslint-enable no-fallthrough */
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.lets.length > 0) {
|
|
||||||
this.scope = scope.child();
|
|
||||||
|
|
||||||
this.lets.forEach((l) => {
|
|
||||||
const dependencies = new Set([l.name.name]);
|
|
||||||
|
|
||||||
l.names.forEach((name) => {
|
|
||||||
this.scope.add(name, dependencies, this);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.scope = scope;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.handlers.forEach((handler) => {
|
|
||||||
handler.modifiers.forEach((modifier) => {
|
|
||||||
if (modifier !== 'once') {
|
|
||||||
return component.error(handler, compiler_errors.invalid_event_modifier_component);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const children = [];
|
|
||||||
for (let i = info.children.length - 1; i >= 0; i--) {
|
|
||||||
const child = info.children[i];
|
|
||||||
if (child.type === 'SlotTemplate') {
|
|
||||||
children.push(child);
|
|
||||||
info.children.splice(i, 1);
|
|
||||||
} else if (
|
|
||||||
(child.type === 'Element' || child.type === 'InlineComponent' || child.type === 'Slot') &&
|
|
||||||
child.attributes.find((attribute) => attribute.name === 'slot')
|
|
||||||
) {
|
|
||||||
const slot_template = {
|
|
||||||
start: child.start,
|
|
||||||
end: child.end,
|
|
||||||
type: 'SlotTemplate',
|
|
||||||
name: 'svelte:fragment',
|
|
||||||
attributes: [],
|
|
||||||
children: [child]
|
|
||||||
};
|
|
||||||
|
|
||||||
// transfer attributes
|
|
||||||
for (let i = child.attributes.length - 1; i >= 0; i--) {
|
|
||||||
const attribute = child.attributes[i];
|
|
||||||
if (attribute.type === 'Let') {
|
|
||||||
slot_template.attributes.push(attribute);
|
|
||||||
child.attributes.splice(i, 1);
|
|
||||||
} else if (attribute.type === 'Attribute' && attribute.name === 'slot') {
|
|
||||||
slot_template.attributes.push(attribute);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// transfer const
|
|
||||||
for (let i = child.children.length - 1; i >= 0; i--) {
|
|
||||||
const child_child = child.children[i];
|
|
||||||
if (child_child.type === 'ConstTag') {
|
|
||||||
slot_template.children.push(child_child);
|
|
||||||
child.children.splice(i, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
children.push(slot_template);
|
|
||||||
info.children.splice(i, 1);
|
|
||||||
} else if (child.type === 'Comment' && children.length > 0) {
|
|
||||||
children[children.length - 1].children.unshift(child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (info.children.some((node) => not_whitespace_text(node))) {
|
|
||||||
children.push({
|
|
||||||
start: info.start,
|
|
||||||
end: info.end,
|
|
||||||
type: 'SlotTemplate',
|
|
||||||
name: 'svelte:fragment',
|
|
||||||
attributes: [],
|
|
||||||
children: info.children
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.children = map_children(component, this, this.scope, children);
|
|
||||||
}
|
|
||||||
|
|
||||||
get slot_template_name() {
|
|
||||||
return this.attributes
|
|
||||||
.find((attribute) => attribute.name === 'slot')
|
|
||||||
.get_static_value() as string;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function not_whitespace_text(node) {
|
|
||||||
return !(node.type === 'Text' && regex_only_whitespaces.test(node.data));
|
|
||||||
}
|
|
||||||
|
|
||||||
function get_namespace(parent: Node, explicit_namespace: string) {
|
|
||||||
const parent_element = parent.find_nearest(/^Element/);
|
|
||||||
|
|
||||||
if (!parent_element) {
|
|
||||||
return explicit_namespace;
|
|
||||||
}
|
|
||||||
|
|
||||||
return parent_element.namespace;
|
|
||||||
}
|
|
@ -0,0 +1,24 @@
|
|||||||
|
import Expression from './shared/Expression.js';
|
||||||
|
import map_children from './shared/map_children.js';
|
||||||
|
import AbstractBlock from './shared/AbstractBlock.js';
|
||||||
|
|
||||||
|
/** @extends AbstractBlock<'KeyBlock'> */
|
||||||
|
export default class KeyBlock extends AbstractBlock {
|
||||||
|
/** @type {import('./shared/Expression.js').default} */
|
||||||
|
expression;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('../Component.js').default} component
|
||||||
|
* @param {import('./shared/Node.js').default} parent
|
||||||
|
* @param {import('./shared/TemplateScope.js').default} scope
|
||||||
|
* @param {import('../../interfaces.js').TemplateNode} info
|
||||||
|
*/
|
||||||
|
constructor(component, parent, scope, info) {
|
||||||
|
super(component, parent, scope, info);
|
||||||
|
this.cannot_use_innerhtml();
|
||||||
|
this.not_static_content();
|
||||||
|
this.expression = new Expression(component, this, scope, info.expression);
|
||||||
|
this.children = map_children(component, this, scope, info.children);
|
||||||
|
this.warn_if_empty_block();
|
||||||
|
}
|
||||||
|
}
|
@ -1,25 +0,0 @@
|
|||||||
import Expression from './shared/Expression';
|
|
||||||
import map_children from './shared/map_children';
|
|
||||||
import AbstractBlock from './shared/AbstractBlock';
|
|
||||||
import Component from '../Component';
|
|
||||||
import TemplateScope from './shared/TemplateScope';
|
|
||||||
import { TemplateNode } from '../../interfaces';
|
|
||||||
import Node from './shared/Node';
|
|
||||||
|
|
||||||
export default class KeyBlock extends AbstractBlock {
|
|
||||||
type: 'KeyBlock';
|
|
||||||
|
|
||||||
expression: Expression;
|
|
||||||
|
|
||||||
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
|
|
||||||
super(component, parent, scope, info);
|
|
||||||
this.cannot_use_innerhtml();
|
|
||||||
this.not_static_content();
|
|
||||||
|
|
||||||
this.expression = new Expression(component, this, scope, info.expression);
|
|
||||||
|
|
||||||
this.children = map_children(component, this, scope, info.children);
|
|
||||||
|
|
||||||
this.warn_if_empty_block();
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,52 @@
|
|||||||
|
import Node from './shared/Node.js';
|
||||||
|
import { walk } from 'estree-walker';
|
||||||
|
import compiler_errors from '../compiler_errors.js';
|
||||||
|
|
||||||
|
const applicable = new Set(['Identifier', 'ObjectExpression', 'ArrayExpression', 'Property']);
|
||||||
|
|
||||||
|
/** @extends Node<'Let'> */
|
||||||
|
export default class Let extends Node {
|
||||||
|
/** @type {import('estree').Identifier} */
|
||||||
|
name;
|
||||||
|
|
||||||
|
/** @type {import('estree').Identifier} */
|
||||||
|
value;
|
||||||
|
|
||||||
|
/** @type {string[]} */
|
||||||
|
names = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('../Component.js').default} component
|
||||||
|
* @param {import('./shared/Node.js').default} parent
|
||||||
|
* @param {import('./shared/TemplateScope.js').default} scope
|
||||||
|
* @param {import('../../interfaces.js').TemplateNode} info
|
||||||
|
*/
|
||||||
|
constructor(component, parent, scope, info) {
|
||||||
|
super(component, parent, scope, info);
|
||||||
|
this.name = { type: 'Identifier', name: info.name };
|
||||||
|
const { names } = this;
|
||||||
|
if (info.expression) {
|
||||||
|
this.value = info.expression;
|
||||||
|
walk(info.expression, {
|
||||||
|
/** @param {import('estree').Identifier | import('estree').BasePattern} node */
|
||||||
|
enter(node) {
|
||||||
|
if (!applicable.has(node.type)) {
|
||||||
|
return component.error(/** @type {any} */ (node), compiler_errors.invalid_let);
|
||||||
|
}
|
||||||
|
if (node.type === 'Identifier') {
|
||||||
|
names.push(/** @type {import('estree').Identifier} */ (node).name);
|
||||||
|
}
|
||||||
|
// slightly unfortunate hack
|
||||||
|
if (node.type === 'ArrayExpression') {
|
||||||
|
node.type = 'ArrayPattern';
|
||||||
|
}
|
||||||
|
if (node.type === 'ObjectExpression') {
|
||||||
|
node.type = 'ObjectPattern';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
names.push(this.name.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,51 +0,0 @@
|
|||||||
import Node from './shared/Node';
|
|
||||||
import Component from '../Component';
|
|
||||||
import { walk } from 'estree-walker';
|
|
||||||
import { BasePattern, Identifier } from 'estree';
|
|
||||||
import TemplateScope from './shared/TemplateScope';
|
|
||||||
import { TemplateNode } from '../../interfaces';
|
|
||||||
import compiler_errors from '../compiler_errors';
|
|
||||||
|
|
||||||
const applicable = new Set(['Identifier', 'ObjectExpression', 'ArrayExpression', 'Property']);
|
|
||||||
|
|
||||||
export default class Let extends Node {
|
|
||||||
type: 'Let';
|
|
||||||
name: Identifier;
|
|
||||||
value: Identifier;
|
|
||||||
names: string[] = [];
|
|
||||||
|
|
||||||
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
|
|
||||||
super(component, parent, scope, info);
|
|
||||||
|
|
||||||
this.name = { type: 'Identifier', name: info.name };
|
|
||||||
|
|
||||||
const { names } = this;
|
|
||||||
|
|
||||||
if (info.expression) {
|
|
||||||
this.value = info.expression;
|
|
||||||
|
|
||||||
walk(info.expression, {
|
|
||||||
enter(node: Identifier | BasePattern) {
|
|
||||||
if (!applicable.has(node.type)) {
|
|
||||||
return component.error(node as any, compiler_errors.invalid_let);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node.type === 'Identifier') {
|
|
||||||
names.push((node as Identifier).name);
|
|
||||||
}
|
|
||||||
|
|
||||||
// slightly unfortunate hack
|
|
||||||
if (node.type === 'ArrayExpression') {
|
|
||||||
node.type = 'ArrayPattern';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node.type === 'ObjectExpression') {
|
|
||||||
node.type = 'ObjectPattern';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
names.push(this.name.name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,4 @@
|
|||||||
|
import Tag from './shared/Tag.js';
|
||||||
|
|
||||||
|
/** @extends Tag<'MustacheTag'> */
|
||||||
|
export default class MustacheTag extends Tag {}
|
@ -1,5 +0,0 @@
|
|||||||
import Tag from './shared/Tag';
|
|
||||||
|
|
||||||
export default class MustacheTag extends Tag {
|
|
||||||
type: 'MustacheTag';
|
|
||||||
}
|
|
@ -0,0 +1,4 @@
|
|||||||
|
import Node from './shared/Node.js';
|
||||||
|
|
||||||
|
/** @extends Node<'Options'> */
|
||||||
|
export default class Options extends Node {}
|
@ -1,5 +0,0 @@
|
|||||||
import Node from './shared/Node';
|
|
||||||
|
|
||||||
export default class Options extends Node {
|
|
||||||
type: 'Options';
|
|
||||||
}
|
|
@ -0,0 +1,19 @@
|
|||||||
|
import map_children from './shared/map_children.js';
|
||||||
|
import AbstractBlock from './shared/AbstractBlock.js';
|
||||||
|
|
||||||
|
/** @extends AbstractBlock<'PendingBlock'> */
|
||||||
|
export default class PendingBlock extends AbstractBlock {
|
||||||
|
/**
|
||||||
|
* @param {import('../Component.js').default} component
|
||||||
|
* @param {import('./shared/Node.js').default} parent
|
||||||
|
* @param {import('./shared/TemplateScope.js').default} scope
|
||||||
|
* @param {import('../../interfaces.js').TemplateNode} info
|
||||||
|
*/
|
||||||
|
constructor(component, parent, scope, info) {
|
||||||
|
super(component, parent, scope, info);
|
||||||
|
this.children = map_children(component, parent, scope, info.children);
|
||||||
|
if (!info.skip) {
|
||||||
|
this.warn_if_empty_block();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,18 +0,0 @@
|
|||||||
import map_children from './shared/map_children';
|
|
||||||
import AbstractBlock from './shared/AbstractBlock';
|
|
||||||
import Component from '../Component';
|
|
||||||
import TemplateScope from './shared/TemplateScope';
|
|
||||||
import { TemplateNode } from '../../interfaces';
|
|
||||||
import Node from './shared/Node';
|
|
||||||
|
|
||||||
export default class PendingBlock extends AbstractBlock {
|
|
||||||
type: 'PendingBlock';
|
|
||||||
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
|
|
||||||
super(component, parent, scope, info);
|
|
||||||
this.children = map_children(component, parent, scope, info.children);
|
|
||||||
|
|
||||||
if (!info.skip) {
|
|
||||||
this.warn_if_empty_block();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +1,13 @@
|
|||||||
import Tag from './shared/Tag';
|
import Tag from './shared/Tag.js';
|
||||||
|
|
||||||
|
/** @extends Tag<'RawMustacheTag'> */
|
||||||
export default class RawMustacheTag extends Tag {
|
export default class RawMustacheTag extends Tag {
|
||||||
type: 'RawMustacheTag';
|
/**
|
||||||
|
* @param {any} component
|
||||||
|
* @param {any} parent
|
||||||
|
* @param {any} scope
|
||||||
|
* @param {any} info
|
||||||
|
*/
|
||||||
constructor(component, parent, scope, info) {
|
constructor(component, parent, scope, info) {
|
||||||
super(component, parent, scope, info);
|
super(component, parent, scope, info);
|
||||||
this.cannot_use_innerhtml();
|
this.cannot_use_innerhtml();
|
@ -0,0 +1,80 @@
|
|||||||
|
import Element from './Element.js';
|
||||||
|
import Attribute from './Attribute.js';
|
||||||
|
import compiler_errors from '../compiler_errors.js';
|
||||||
|
|
||||||
|
/** @extends Element */
|
||||||
|
export default class Slot extends Element {
|
||||||
|
/** @type {'Slot'} */
|
||||||
|
// @ts-ignore Slot elements have the 'Slot' type, but TypeScript doesn't allow us to have 'Slot' when it extends Element
|
||||||
|
type = 'Slot';
|
||||||
|
|
||||||
|
/** @type {string} */
|
||||||
|
slot_name;
|
||||||
|
|
||||||
|
/** @type {Map<string, import('./Attribute.js').default>} */
|
||||||
|
values = new Map();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('../Component.js').default} component
|
||||||
|
* @param {import('./interfaces.js').INode} parent
|
||||||
|
* @param {import('./shared/TemplateScope.js').default} scope
|
||||||
|
* @param {import('../../interfaces.js').TemplateNode} info
|
||||||
|
*/
|
||||||
|
constructor(component, parent, scope, info) {
|
||||||
|
super(component, parent, scope, info);
|
||||||
|
info.attributes.forEach(
|
||||||
|
/** @param {any} attr */ (attr) => {
|
||||||
|
if (attr.type !== 'Attribute' && attr.type !== 'Spread') {
|
||||||
|
return component.error(attr, compiler_errors.invalid_slot_directive);
|
||||||
|
}
|
||||||
|
if (attr.name === 'name') {
|
||||||
|
if (attr.value.length !== 1 || attr.value[0].type !== 'Text') {
|
||||||
|
return component.error(attr, compiler_errors.dynamic_slot_name);
|
||||||
|
}
|
||||||
|
this.slot_name = attr.value[0].data;
|
||||||
|
if (this.slot_name === 'default') {
|
||||||
|
return component.error(attr, compiler_errors.invalid_slot_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.values.set(attr.name, new Attribute(component, this, scope, attr));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (!this.slot_name) this.slot_name = 'default';
|
||||||
|
if (this.slot_name === 'default') {
|
||||||
|
// if this is the default slot, add our dependencies to any
|
||||||
|
// other slots (which inherit our slot values) that were
|
||||||
|
// previously encountered
|
||||||
|
component.slots.forEach(
|
||||||
|
/** @param {any} slot */ (slot) => {
|
||||||
|
this.values.forEach(
|
||||||
|
/**
|
||||||
|
* @param {any} attribute
|
||||||
|
* @param {any} name
|
||||||
|
*/ (attribute, name) => {
|
||||||
|
if (!slot.values.has(name)) {
|
||||||
|
slot.values.set(name, attribute);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else if (component.slots.has('default')) {
|
||||||
|
// otherwise, go the other way — inherit values from
|
||||||
|
// a previously encountered default slot
|
||||||
|
const default_slot = component.slots.get('default');
|
||||||
|
default_slot.values.forEach(
|
||||||
|
/**
|
||||||
|
* @param {any} attribute
|
||||||
|
* @param {any} name
|
||||||
|
*/ (attribute, name) => {
|
||||||
|
if (!this.values.has(name)) {
|
||||||
|
this.values.set(name, attribute);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
component.slots.set(this.slot_name, this);
|
||||||
|
this.cannot_use_innerhtml();
|
||||||
|
this.not_static_content();
|
||||||
|
}
|
||||||
|
}
|
@ -1,68 +0,0 @@
|
|||||||
import Element from './Element';
|
|
||||||
import Attribute from './Attribute';
|
|
||||||
import Component from '../Component';
|
|
||||||
import TemplateScope from './shared/TemplateScope';
|
|
||||||
import { INode } from './interfaces';
|
|
||||||
import { TemplateNode } from '../../interfaces';
|
|
||||||
import compiler_errors from '../compiler_errors';
|
|
||||||
|
|
||||||
export default class Slot extends Element {
|
|
||||||
// @ts-ignore Slot elements have the 'Slot' type, but TypeScript doesn't allow us to have 'Slot' when it extends Element
|
|
||||||
type: 'Slot';
|
|
||||||
name: string;
|
|
||||||
children: INode[];
|
|
||||||
slot_name: string;
|
|
||||||
values: Map<string, Attribute> = new Map();
|
|
||||||
|
|
||||||
constructor(component: Component, parent: INode, scope: TemplateScope, info: TemplateNode) {
|
|
||||||
super(component, parent, scope, info);
|
|
||||||
|
|
||||||
info.attributes.forEach((attr) => {
|
|
||||||
if (attr.type !== 'Attribute' && attr.type !== 'Spread') {
|
|
||||||
return component.error(attr, compiler_errors.invalid_slot_directive);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (attr.name === 'name') {
|
|
||||||
if (attr.value.length !== 1 || attr.value[0].type !== 'Text') {
|
|
||||||
return component.error(attr, compiler_errors.dynamic_slot_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.slot_name = attr.value[0].data;
|
|
||||||
if (this.slot_name === 'default') {
|
|
||||||
return component.error(attr, compiler_errors.invalid_slot_name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.values.set(attr.name, new Attribute(component, this, scope, attr));
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!this.slot_name) this.slot_name = 'default';
|
|
||||||
|
|
||||||
if (this.slot_name === 'default') {
|
|
||||||
// if this is the default slot, add our dependencies to any
|
|
||||||
// other slots (which inherit our slot values) that were
|
|
||||||
// previously encountered
|
|
||||||
component.slots.forEach((slot) => {
|
|
||||||
this.values.forEach((attribute, name) => {
|
|
||||||
if (!slot.values.has(name)) {
|
|
||||||
slot.values.set(name, attribute);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else if (component.slots.has('default')) {
|
|
||||||
// otherwise, go the other way — inherit values from
|
|
||||||
// a previously encountered default slot
|
|
||||||
const default_slot = component.slots.get('default');
|
|
||||||
default_slot.values.forEach((attribute, name) => {
|
|
||||||
if (!this.values.has(name)) {
|
|
||||||
this.values.set(name, attribute);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
component.slots.set(this.slot_name, this);
|
|
||||||
|
|
||||||
this.cannot_use_innerhtml();
|
|
||||||
this.not_static_content();
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,79 @@
|
|||||||
|
import Node from './shared/Node.js';
|
||||||
|
import Let from './Let.js';
|
||||||
|
import Attribute from './Attribute.js';
|
||||||
|
import compiler_errors from '../compiler_errors.js';
|
||||||
|
import get_const_tags from './shared/get_const_tags.js';
|
||||||
|
|
||||||
|
/** @extends Node<'SlotTemplate'> */
|
||||||
|
export default class SlotTemplate extends Node {
|
||||||
|
/** @type {import('./shared/TemplateScope.js').default} */
|
||||||
|
scope;
|
||||||
|
|
||||||
|
/** @type {import('./interfaces.js').INode[]} */
|
||||||
|
children;
|
||||||
|
|
||||||
|
/** @type {import('./Let.js').default[]} */
|
||||||
|
lets = [];
|
||||||
|
|
||||||
|
/** @type {import('./ConstTag.js').default[]} */
|
||||||
|
const_tags;
|
||||||
|
|
||||||
|
/** @type {import('./Attribute.js').default} */
|
||||||
|
slot_attribute;
|
||||||
|
|
||||||
|
/** @type {string} */
|
||||||
|
slot_template_name = 'default';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('../Component.js').default} component
|
||||||
|
* @param {import('./interfaces.js').INode} parent
|
||||||
|
* @param {import('./shared/TemplateScope.js').default} scope
|
||||||
|
* @param {any} info
|
||||||
|
*/
|
||||||
|
constructor(component, parent, scope, info) {
|
||||||
|
super(component, parent, scope, info);
|
||||||
|
this.validate_slot_template_placement();
|
||||||
|
scope = scope.child();
|
||||||
|
info.attributes.forEach(
|
||||||
|
/** @param {any} node */ (node) => {
|
||||||
|
switch (node.type) {
|
||||||
|
case 'Let': {
|
||||||
|
const l = new Let(component, this, scope, node);
|
||||||
|
this.lets.push(l);
|
||||||
|
const dependencies = new Set([l.name.name]);
|
||||||
|
l.names.forEach(
|
||||||
|
/** @param {any} name */ (name) => {
|
||||||
|
scope.add(name, dependencies, this);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'Attribute': {
|
||||||
|
if (node.name === 'slot') {
|
||||||
|
this.slot_attribute = new Attribute(component, this, scope, node);
|
||||||
|
if (!this.slot_attribute.is_static) {
|
||||||
|
return component.error(node, compiler_errors.invalid_slot_attribute);
|
||||||
|
}
|
||||||
|
const value = this.slot_attribute.get_static_value();
|
||||||
|
if (typeof value === 'boolean') {
|
||||||
|
return component.error(node, compiler_errors.invalid_slot_attribute_value_missing);
|
||||||
|
}
|
||||||
|
this.slot_template_name = /** @type {string} */ (value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
throw new Error(`Invalid attribute '${node.name}' in <svelte:fragment>`);
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new Error(`Not implemented: ${node.type}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
this.scope = scope;
|
||||||
|
[this.const_tags, this.children] = get_const_tags(info.children, component, this, this);
|
||||||
|
}
|
||||||
|
validate_slot_template_placement() {
|
||||||
|
if (this.parent.type !== 'InlineComponent') {
|
||||||
|
return this.component.error(this, compiler_errors.invalid_slotted_content_fragment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,68 +0,0 @@
|
|||||||
import Component from '../Component';
|
|
||||||
import TemplateScope from './shared/TemplateScope';
|
|
||||||
import Node from './shared/Node';
|
|
||||||
import Let from './Let';
|
|
||||||
import Attribute from './Attribute';
|
|
||||||
import { INode } from './interfaces';
|
|
||||||
import compiler_errors from '../compiler_errors';
|
|
||||||
import get_const_tags from './shared/get_const_tags';
|
|
||||||
import ConstTag from './ConstTag';
|
|
||||||
|
|
||||||
export default class SlotTemplate extends Node {
|
|
||||||
type: 'SlotTemplate';
|
|
||||||
scope: TemplateScope;
|
|
||||||
children: INode[];
|
|
||||||
lets: Let[] = [];
|
|
||||||
const_tags: ConstTag[];
|
|
||||||
slot_attribute: Attribute;
|
|
||||||
slot_template_name: string = 'default';
|
|
||||||
|
|
||||||
constructor(component: Component, parent: INode, scope: TemplateScope, info: any) {
|
|
||||||
super(component, parent, scope, info);
|
|
||||||
|
|
||||||
this.validate_slot_template_placement();
|
|
||||||
|
|
||||||
scope = scope.child();
|
|
||||||
|
|
||||||
info.attributes.forEach((node) => {
|
|
||||||
switch (node.type) {
|
|
||||||
case 'Let': {
|
|
||||||
const l = new Let(component, this, scope, node);
|
|
||||||
this.lets.push(l);
|
|
||||||
const dependencies = new Set([l.name.name]);
|
|
||||||
|
|
||||||
l.names.forEach((name) => {
|
|
||||||
scope.add(name, dependencies, this);
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'Attribute': {
|
|
||||||
if (node.name === 'slot') {
|
|
||||||
this.slot_attribute = new Attribute(component, this, scope, node);
|
|
||||||
if (!this.slot_attribute.is_static) {
|
|
||||||
return component.error(node, compiler_errors.invalid_slot_attribute);
|
|
||||||
}
|
|
||||||
const value = this.slot_attribute.get_static_value();
|
|
||||||
if (typeof value === 'boolean') {
|
|
||||||
return component.error(node, compiler_errors.invalid_slot_attribute_value_missing);
|
|
||||||
}
|
|
||||||
this.slot_template_name = value as string;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
throw new Error(`Invalid attribute '${node.name}' in <svelte:fragment>`);
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
throw new Error(`Not implemented: ${node.type}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.scope = scope;
|
|
||||||
[this.const_tags, this.children] = get_const_tags(info.children, component, this, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
validate_slot_template_placement() {
|
|
||||||
if (this.parent.type !== 'InlineComponent') {
|
|
||||||
return this.component.error(this, compiler_errors.invalid_slotted_content_fragment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,34 @@
|
|||||||
|
import AbstractBlock from './shared/AbstractBlock.js';
|
||||||
|
import get_const_tags from './shared/get_const_tags.js';
|
||||||
|
|
||||||
|
/** @extends AbstractBlock<'ThenBlock'> */
|
||||||
|
export default class ThenBlock extends AbstractBlock {
|
||||||
|
/** @type {import('./shared/TemplateScope.js').default} */
|
||||||
|
scope;
|
||||||
|
|
||||||
|
/** @type {import('./ConstTag.js').default[]} */
|
||||||
|
const_tags;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('../Component.js').default} component
|
||||||
|
* @param {import('./AwaitBlock.js').default} parent
|
||||||
|
* @param {import('./shared/TemplateScope.js').default} scope
|
||||||
|
* @param {import('../../interfaces.js').TemplateNode} info
|
||||||
|
*/
|
||||||
|
constructor(component, parent, scope, info) {
|
||||||
|
super(component, parent, scope, info);
|
||||||
|
this.scope = scope.child();
|
||||||
|
if (parent.then_node) {
|
||||||
|
parent.then_contexts.forEach(
|
||||||
|
/** @param {any} context */ (context) => {
|
||||||
|
if (context.type !== 'DestructuredVariable') return;
|
||||||
|
this.scope.add(context.key.name, parent.expression.dependencies, this);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
[this.const_tags, this.children] = get_const_tags(info.children, component, this, parent);
|
||||||
|
if (!info.skip) {
|
||||||
|
this.warn_if_empty_block();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,31 +0,0 @@
|
|||||||
import TemplateScope from './shared/TemplateScope';
|
|
||||||
import AbstractBlock from './shared/AbstractBlock';
|
|
||||||
import AwaitBlock from './AwaitBlock';
|
|
||||||
import Component from '../Component';
|
|
||||||
import { TemplateNode } from '../../interfaces';
|
|
||||||
import get_const_tags from './shared/get_const_tags';
|
|
||||||
import ConstTag from './ConstTag';
|
|
||||||
|
|
||||||
export default class ThenBlock extends AbstractBlock {
|
|
||||||
type: 'ThenBlock';
|
|
||||||
scope: TemplateScope;
|
|
||||||
const_tags: ConstTag[];
|
|
||||||
|
|
||||||
constructor(component: Component, parent: AwaitBlock, scope: TemplateScope, info: TemplateNode) {
|
|
||||||
super(component, parent, scope, info);
|
|
||||||
|
|
||||||
this.scope = scope.child();
|
|
||||||
if (parent.then_node) {
|
|
||||||
parent.then_contexts.forEach((context) => {
|
|
||||||
if (context.type !== 'DestructuredVariable') return;
|
|
||||||
this.scope.add(context.key.name, parent.expression.dependencies, this);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
[this.const_tags, this.children] = get_const_tags(info.children, component, this, parent);
|
|
||||||
|
|
||||||
if (!info.skip) {
|
|
||||||
this.warn_if_empty_block();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,38 @@
|
|||||||
|
import Node from './shared/Node.js';
|
||||||
|
import map_children from './shared/map_children.js';
|
||||||
|
import compiler_errors from '../compiler_errors.js';
|
||||||
|
|
||||||
|
/** @extends Node<'Title'> */
|
||||||
|
export default class Title extends Node {
|
||||||
|
/** @type {import('./shared/map_children.js').Children} */
|
||||||
|
children;
|
||||||
|
|
||||||
|
/** @type {boolean} */
|
||||||
|
should_cache;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('../Component.js').default} component
|
||||||
|
* @param {import('./shared/Node.js').default} parent
|
||||||
|
* @param {import('./shared/TemplateScope.js').default} scope
|
||||||
|
* @param {import('../../interfaces.js').TemplateNode} info
|
||||||
|
*/
|
||||||
|
constructor(component, parent, scope, info) {
|
||||||
|
super(component, parent, scope, info);
|
||||||
|
this.children = map_children(component, parent, scope, info.children);
|
||||||
|
if (info.attributes.length > 0) {
|
||||||
|
component.error(info.attributes[0], compiler_errors.illegal_attribute_title);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
info.children.forEach(
|
||||||
|
/** @param {any} child */ (child) => {
|
||||||
|
if (child.type !== 'Text' && child.type !== 'MustacheTag') {
|
||||||
|
return component.error(child, compiler_errors.illegal_structure_title);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
this.should_cache =
|
||||||
|
info.children.length === 1
|
||||||
|
? info.children[0].type !== 'Identifier' || scope.names.has(info.children[0].name)
|
||||||
|
: true;
|
||||||
|
}
|
||||||
|
}
|
@ -1,33 +0,0 @@
|
|||||||
import Node from './shared/Node';
|
|
||||||
import map_children, { Children } from './shared/map_children';
|
|
||||||
import Component from '../Component';
|
|
||||||
import TemplateScope from './shared/TemplateScope';
|
|
||||||
import { TemplateNode } from '../../interfaces';
|
|
||||||
import compiler_errors from '../compiler_errors';
|
|
||||||
|
|
||||||
export default class Title extends Node {
|
|
||||||
type: 'Title';
|
|
||||||
children: Children;
|
|
||||||
should_cache: boolean;
|
|
||||||
|
|
||||||
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
|
|
||||||
super(component, parent, scope, info);
|
|
||||||
this.children = map_children(component, parent, scope, info.children);
|
|
||||||
|
|
||||||
if (info.attributes.length > 0) {
|
|
||||||
component.error(info.attributes[0], compiler_errors.illegal_attribute_title);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
info.children.forEach((child) => {
|
|
||||||
if (child.type !== 'Text' && child.type !== 'MustacheTag') {
|
|
||||||
return component.error(child, compiler_errors.illegal_structure_title);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.should_cache =
|
|
||||||
info.children.length === 1
|
|
||||||
? info.children[0].type !== 'Identifier' || scope.names.has(info.children[0].name)
|
|
||||||
: true;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,44 @@
|
|||||||
|
import Node from './shared/Node.js';
|
||||||
|
import Expression from './shared/Expression.js';
|
||||||
|
import compiler_errors from '../compiler_errors.js';
|
||||||
|
|
||||||
|
/** @extends Node<'Transition'> */
|
||||||
|
export default class Transition extends Node {
|
||||||
|
/** @type {string} */
|
||||||
|
name;
|
||||||
|
|
||||||
|
/** @type {string} */
|
||||||
|
directive;
|
||||||
|
|
||||||
|
/** @type {import('./shared/Expression.js').default} */
|
||||||
|
expression;
|
||||||
|
|
||||||
|
/** @type {boolean} */
|
||||||
|
is_local;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('../Component.js').default} component
|
||||||
|
* @param {import('./Element.js').default} parent
|
||||||
|
* @param {import('./shared/TemplateScope.js').default} scope
|
||||||
|
* @param {import('../../interfaces.js').TemplateNode} info
|
||||||
|
*/
|
||||||
|
constructor(component, parent, scope, info) {
|
||||||
|
super(component, parent, scope, info);
|
||||||
|
component.warn_if_undefined(info.name, info, scope);
|
||||||
|
this.name = info.name;
|
||||||
|
component.add_reference(/** @type {any} */ (this), info.name.split('.')[0]);
|
||||||
|
this.directive = info.intro && info.outro ? 'transition' : info.intro ? 'in' : 'out';
|
||||||
|
this.is_local = info.modifiers.includes('local');
|
||||||
|
if ((info.intro && parent.intro) || (info.outro && parent.outro)) {
|
||||||
|
const parent_transition = parent.intro || parent.outro;
|
||||||
|
component.error(
|
||||||
|
info,
|
||||||
|
compiler_errors.duplicate_transition(this.directive, parent_transition.directive)
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.expression = info.expression
|
||||||
|
? new Expression(component, this, scope, info.expression)
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
}
|
@ -1,40 +0,0 @@
|
|||||||
import Node from './shared/Node';
|
|
||||||
import Expression from './shared/Expression';
|
|
||||||
import Component from '../Component';
|
|
||||||
import TemplateScope from './shared/TemplateScope';
|
|
||||||
import { TemplateNode } from '../../interfaces';
|
|
||||||
import Element from './Element';
|
|
||||||
import compiler_errors from '../compiler_errors';
|
|
||||||
|
|
||||||
export default class Transition extends Node {
|
|
||||||
type: 'Transition';
|
|
||||||
name: string;
|
|
||||||
directive: string;
|
|
||||||
expression: Expression;
|
|
||||||
is_local: boolean;
|
|
||||||
|
|
||||||
constructor(component: Component, parent: Element, scope: TemplateScope, info: TemplateNode) {
|
|
||||||
super(component, parent, scope, info);
|
|
||||||
|
|
||||||
component.warn_if_undefined(info.name, info, scope);
|
|
||||||
|
|
||||||
this.name = info.name;
|
|
||||||
component.add_reference(this as any, info.name.split('.')[0]);
|
|
||||||
|
|
||||||
this.directive = info.intro && info.outro ? 'transition' : info.intro ? 'in' : 'out';
|
|
||||||
this.is_local = info.modifiers.includes('local');
|
|
||||||
|
|
||||||
if ((info.intro && parent.intro) || (info.outro && parent.outro)) {
|
|
||||||
const parent_transition = parent.intro || parent.outro;
|
|
||||||
component.error(
|
|
||||||
info,
|
|
||||||
compiler_errors.duplicate_transition(this.directive, parent_transition.directive)
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.expression = info.expression
|
|
||||||
? new Expression(component, this, scope, info.expression)
|
|
||||||
: null;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,86 @@
|
|||||||
|
import Node from './shared/Node.js';
|
||||||
|
import Binding from './Binding.js';
|
||||||
|
import EventHandler from './EventHandler.js';
|
||||||
|
import flatten_reference from '../utils/flatten_reference.js';
|
||||||
|
import fuzzymatch from '../../utils/fuzzymatch.js';
|
||||||
|
import list from '../../utils/list.js';
|
||||||
|
import Action from './Action.js';
|
||||||
|
import compiler_errors from '../compiler_errors.js';
|
||||||
|
|
||||||
|
const valid_bindings = [
|
||||||
|
'innerWidth',
|
||||||
|
'innerHeight',
|
||||||
|
'outerWidth',
|
||||||
|
'outerHeight',
|
||||||
|
'scrollX',
|
||||||
|
'scrollY',
|
||||||
|
'devicePixelRatio',
|
||||||
|
'online'
|
||||||
|
];
|
||||||
|
|
||||||
|
/** @extends Node<'Window'> */
|
||||||
|
export default class Window extends Node {
|
||||||
|
/** @type {import('./EventHandler.js').default[]} */
|
||||||
|
handlers = [];
|
||||||
|
|
||||||
|
/** @type {import('./Binding.js').default[]} */
|
||||||
|
bindings = [];
|
||||||
|
|
||||||
|
/** @type {import('./Action.js').default[]} */
|
||||||
|
actions = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('../Component.js').default} component
|
||||||
|
* @param {import('./shared/Node.js').default} parent
|
||||||
|
* @param {import('./shared/TemplateScope.js').default} scope
|
||||||
|
* @param {import('../../interfaces.js').TemplateNode} info
|
||||||
|
*/
|
||||||
|
constructor(component, parent, scope, info) {
|
||||||
|
super(component, parent, scope, info);
|
||||||
|
info.attributes.forEach(
|
||||||
|
/** @param {any} node */ (node) => {
|
||||||
|
if (node.type === 'EventHandler') {
|
||||||
|
this.handlers.push(new EventHandler(component, this, scope, node));
|
||||||
|
} else if (node.type === 'Binding') {
|
||||||
|
if (node.expression.type !== 'Identifier') {
|
||||||
|
const { parts } = flatten_reference(node.expression);
|
||||||
|
// TODO is this constraint necessary?
|
||||||
|
return component.error(node.expression, compiler_errors.invalid_binding_window(parts));
|
||||||
|
}
|
||||||
|
if (!~valid_bindings.indexOf(node.name)) {
|
||||||
|
const match =
|
||||||
|
node.name === 'width'
|
||||||
|
? 'innerWidth'
|
||||||
|
: node.name === 'height'
|
||||||
|
? 'innerHeight'
|
||||||
|
: fuzzymatch(node.name, valid_bindings);
|
||||||
|
if (match) {
|
||||||
|
return component.error(
|
||||||
|
node,
|
||||||
|
compiler_errors.invalid_binding_on(
|
||||||
|
node.name,
|
||||||
|
'<svelte:window>',
|
||||||
|
` (did you mean '${match}'?)`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return component.error(
|
||||||
|
node,
|
||||||
|
compiler_errors.invalid_binding_on(
|
||||||
|
node.name,
|
||||||
|
'<svelte:window>',
|
||||||
|
` — valid bindings are ${list(valid_bindings)}`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.bindings.push(new Binding(component, this, scope, node));
|
||||||
|
} else if (node.type === 'Action') {
|
||||||
|
this.actions.push(new Action(component, this, scope, node));
|
||||||
|
} else {
|
||||||
|
// TODO there shouldn't be anything else here...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,81 +0,0 @@
|
|||||||
import Node from './shared/Node';
|
|
||||||
import Binding from './Binding';
|
|
||||||
import EventHandler from './EventHandler';
|
|
||||||
import flatten_reference from '../utils/flatten_reference';
|
|
||||||
import fuzzymatch from '../../utils/fuzzymatch';
|
|
||||||
import list from '../../utils/list';
|
|
||||||
import Action from './Action';
|
|
||||||
import Component from '../Component';
|
|
||||||
import TemplateScope from './shared/TemplateScope';
|
|
||||||
import { TemplateNode } from '../../interfaces';
|
|
||||||
import compiler_errors from '../compiler_errors';
|
|
||||||
|
|
||||||
const valid_bindings = [
|
|
||||||
'innerWidth',
|
|
||||||
'innerHeight',
|
|
||||||
'outerWidth',
|
|
||||||
'outerHeight',
|
|
||||||
'scrollX',
|
|
||||||
'scrollY',
|
|
||||||
'devicePixelRatio',
|
|
||||||
'online'
|
|
||||||
];
|
|
||||||
|
|
||||||
export default class Window extends Node {
|
|
||||||
type: 'Window';
|
|
||||||
handlers: EventHandler[] = [];
|
|
||||||
bindings: Binding[] = [];
|
|
||||||
actions: Action[] = [];
|
|
||||||
|
|
||||||
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
|
|
||||||
super(component, parent, scope, info);
|
|
||||||
|
|
||||||
info.attributes.forEach((node) => {
|
|
||||||
if (node.type === 'EventHandler') {
|
|
||||||
this.handlers.push(new EventHandler(component, this, scope, node));
|
|
||||||
} else if (node.type === 'Binding') {
|
|
||||||
if (node.expression.type !== 'Identifier') {
|
|
||||||
const { parts } = flatten_reference(node.expression);
|
|
||||||
|
|
||||||
// TODO is this constraint necessary?
|
|
||||||
return component.error(node.expression, compiler_errors.invalid_binding_window(parts));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!~valid_bindings.indexOf(node.name)) {
|
|
||||||
const match =
|
|
||||||
node.name === 'width'
|
|
||||||
? 'innerWidth'
|
|
||||||
: node.name === 'height'
|
|
||||||
? 'innerHeight'
|
|
||||||
: fuzzymatch(node.name, valid_bindings);
|
|
||||||
|
|
||||||
if (match) {
|
|
||||||
return component.error(
|
|
||||||
node,
|
|
||||||
compiler_errors.invalid_binding_on(
|
|
||||||
node.name,
|
|
||||||
'<svelte:window>',
|
|
||||||
` (did you mean '${match}'?)`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return component.error(
|
|
||||||
node,
|
|
||||||
compiler_errors.invalid_binding_on(
|
|
||||||
node.name,
|
|
||||||
'<svelte:window>',
|
|
||||||
` — valid bindings are ${list(valid_bindings)}`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.bindings.push(new Binding(component, this, scope, node));
|
|
||||||
} else if (node.type === 'Action') {
|
|
||||||
this.actions.push(new Action(component, this, scope, node));
|
|
||||||
} else {
|
|
||||||
// TODO there shouldn't be anything else here...
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +1,4 @@
|
|||||||
import Tag from './shared/Tag';
|
import Tag from './shared/Tag';
|
||||||
|
|
||||||
import Action from './Action';
|
import Action from './Action';
|
||||||
import Animation from './Animation';
|
import Animation from './Animation';
|
||||||
import Attribute from './Attribute';
|
import Attribute from './Attribute';
|
@ -0,0 +1,33 @@
|
|||||||
|
import Node from './Node.js';
|
||||||
|
import compiler_warnings from '../../compiler_warnings.js';
|
||||||
|
|
||||||
|
const regex_non_whitespace_characters = /[^ \r\n\f\v\t]/;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template {string} Type
|
||||||
|
* @extends Node<Type>
|
||||||
|
*/
|
||||||
|
export default class AbstractBlock extends Node {
|
||||||
|
/** @type {import('../../render_dom/Block.js').default} */
|
||||||
|
block;
|
||||||
|
|
||||||
|
/** @type {import('../interfaces.js').INode[]} */
|
||||||
|
children;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('../../Component.js').default} component
|
||||||
|
* @param {any} parent
|
||||||
|
* @param {any} scope
|
||||||
|
* @param {any} info
|
||||||
|
*/
|
||||||
|
constructor(component, parent, scope, info) {
|
||||||
|
super(component, parent, scope, info);
|
||||||
|
}
|
||||||
|
warn_if_empty_block() {
|
||||||
|
if (!this.children || this.children.length > 1) return;
|
||||||
|
const child = this.children[0];
|
||||||
|
if (!child || (child.type === 'Text' && !regex_non_whitespace_characters.test(child.data))) {
|
||||||
|
this.component.warn(this, compiler_warnings.empty_block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,26 +0,0 @@
|
|||||||
import Block from '../../render_dom/Block';
|
|
||||||
import Component from '../../Component';
|
|
||||||
import Node from './Node';
|
|
||||||
import { INode } from '../interfaces';
|
|
||||||
import compiler_warnings from '../../compiler_warnings';
|
|
||||||
|
|
||||||
const regex_non_whitespace_characters = /[^ \r\n\f\v\t]/;
|
|
||||||
|
|
||||||
export default class AbstractBlock extends Node {
|
|
||||||
block: Block;
|
|
||||||
children: INode[];
|
|
||||||
|
|
||||||
constructor(component: Component, parent, scope, info: any) {
|
|
||||||
super(component, parent, scope, info);
|
|
||||||
}
|
|
||||||
|
|
||||||
warn_if_empty_block() {
|
|
||||||
if (!this.children || this.children.length > 1) return;
|
|
||||||
|
|
||||||
const child = this.children[0];
|
|
||||||
|
|
||||||
if (!child || (child.type === 'Text' && !regex_non_whitespace_characters.test(child.data))) {
|
|
||||||
this.component.warn(this, compiler_warnings.empty_block);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,113 @@
|
|||||||
|
/**
|
||||||
|
* @template {string} Type
|
||||||
|
* @template {import('../interfaces.js').INode} [Parent=import('../interfaces.js').INode]
|
||||||
|
*/
|
||||||
|
export default class Node {
|
||||||
|
/**
|
||||||
|
* @readonly
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
start;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @readonly
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
end;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @readonly
|
||||||
|
* @type {import('../../Component.js').default}
|
||||||
|
*/
|
||||||
|
component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @readonly
|
||||||
|
* @type {Parent}
|
||||||
|
*/
|
||||||
|
parent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @readonly
|
||||||
|
* @type {Type}
|
||||||
|
*/
|
||||||
|
type;
|
||||||
|
|
||||||
|
/** @type {import('../interfaces.js').INode} */
|
||||||
|
prev;
|
||||||
|
|
||||||
|
/** @type {import('../interfaces.js').INode} */
|
||||||
|
next;
|
||||||
|
|
||||||
|
/** @type {boolean} */
|
||||||
|
can_use_innerhtml;
|
||||||
|
|
||||||
|
/** @type {boolean} */
|
||||||
|
is_static_content;
|
||||||
|
|
||||||
|
/** @type {string} */
|
||||||
|
var;
|
||||||
|
|
||||||
|
/** @type {import('../Attribute.js').default[]} */
|
||||||
|
attributes = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('../../Component.js').default} component
|
||||||
|
* @param {Node} parent
|
||||||
|
* @param {any} _scope
|
||||||
|
* @param {import('../../../interfaces.js').TemplateNode} info
|
||||||
|
*/
|
||||||
|
constructor(component, parent, _scope, info) {
|
||||||
|
this.start = info.start;
|
||||||
|
this.end = info.end;
|
||||||
|
this.type = /** @type {Type} */ (info.type);
|
||||||
|
// this makes properties non-enumerable, which makes logging
|
||||||
|
// bearable. might have a performance cost. TODO remove in prod?
|
||||||
|
Object.defineProperties(this, {
|
||||||
|
component: {
|
||||||
|
value: component
|
||||||
|
},
|
||||||
|
parent: {
|
||||||
|
value: parent
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.can_use_innerhtml = true;
|
||||||
|
this.is_static_content = true;
|
||||||
|
}
|
||||||
|
cannot_use_innerhtml() {
|
||||||
|
if (this.can_use_innerhtml !== false) {
|
||||||
|
this.can_use_innerhtml = false;
|
||||||
|
if (this.parent) this.parent.cannot_use_innerhtml();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
not_static_content() {
|
||||||
|
this.is_static_content = false;
|
||||||
|
if (this.parent) this.parent.not_static_content();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {RegExp} selector */
|
||||||
|
find_nearest(selector) {
|
||||||
|
if (selector.test(this.type)) return this;
|
||||||
|
if (this.parent) return this.parent.find_nearest(selector);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {string} name */
|
||||||
|
get_static_attribute_value(name) {
|
||||||
|
const attribute = this.attributes.find(
|
||||||
|
/** @param {import('../Attribute.js').default} attr */
|
||||||
|
(attr) => attr.type === 'Attribute' && attr.name.toLowerCase() === name
|
||||||
|
);
|
||||||
|
if (!attribute) return null;
|
||||||
|
if (attribute.is_true) return true;
|
||||||
|
if (attribute.chunks.length === 0) return '';
|
||||||
|
if (attribute.chunks.length === 1 && attribute.chunks[0].type === 'Text') {
|
||||||
|
return /** @type {import('../Text.js').default} */ (attribute.chunks[0]).data;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {string} type */
|
||||||
|
has_ancestor(type) {
|
||||||
|
return this.parent ? this.parent.type === type || this.parent.has_ancestor(type) : false;
|
||||||
|
}
|
||||||
|
}
|
@ -1,81 +0,0 @@
|
|||||||
import Attribute from '../Attribute';
|
|
||||||
import Component from '../../Component';
|
|
||||||
import { INode } from '../interfaces';
|
|
||||||
import Text from '../Text';
|
|
||||||
import { TemplateNode } from '../../../interfaces';
|
|
||||||
|
|
||||||
export default class Node {
|
|
||||||
readonly start: number;
|
|
||||||
readonly end: number;
|
|
||||||
readonly component: Component;
|
|
||||||
readonly parent: INode;
|
|
||||||
readonly type: string;
|
|
||||||
|
|
||||||
prev?: INode;
|
|
||||||
next?: INode;
|
|
||||||
|
|
||||||
can_use_innerhtml: boolean;
|
|
||||||
is_static_content: boolean;
|
|
||||||
var: string;
|
|
||||||
attributes: Attribute[];
|
|
||||||
|
|
||||||
constructor(component: Component, parent: Node, _scope, info: TemplateNode) {
|
|
||||||
this.start = info.start;
|
|
||||||
this.end = info.end;
|
|
||||||
this.type = info.type;
|
|
||||||
|
|
||||||
// this makes properties non-enumerable, which makes logging
|
|
||||||
// bearable. might have a performance cost. TODO remove in prod?
|
|
||||||
Object.defineProperties(this, {
|
|
||||||
component: {
|
|
||||||
value: component
|
|
||||||
},
|
|
||||||
parent: {
|
|
||||||
value: parent
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.can_use_innerhtml = true;
|
|
||||||
this.is_static_content = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
cannot_use_innerhtml() {
|
|
||||||
if (this.can_use_innerhtml !== false) {
|
|
||||||
this.can_use_innerhtml = false;
|
|
||||||
if (this.parent) this.parent.cannot_use_innerhtml();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
not_static_content() {
|
|
||||||
this.is_static_content = false;
|
|
||||||
if (this.parent) this.parent.not_static_content();
|
|
||||||
}
|
|
||||||
|
|
||||||
find_nearest(selector: RegExp) {
|
|
||||||
if (selector.test(this.type)) return this;
|
|
||||||
if (this.parent) return this.parent.find_nearest(selector);
|
|
||||||
}
|
|
||||||
|
|
||||||
get_static_attribute_value(name: string) {
|
|
||||||
const attribute =
|
|
||||||
this.attributes &&
|
|
||||||
this.attributes.find(
|
|
||||||
(attr: Attribute) => attr.type === 'Attribute' && attr.name.toLowerCase() === name
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!attribute) return null;
|
|
||||||
|
|
||||||
if (attribute.is_true) return true;
|
|
||||||
if (attribute.chunks.length === 0) return '';
|
|
||||||
|
|
||||||
if (attribute.chunks.length === 1 && attribute.chunks[0].type === 'Text') {
|
|
||||||
return (attribute.chunks[0] as Text).data;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
has_ancestor(type: string) {
|
|
||||||
return this.parent ? this.parent.type === type || this.parent.has_ancestor(type) : false;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,18 +1,28 @@
|
|||||||
import Node from './Node';
|
import Node from './Node.js';
|
||||||
import Expression from './Expression';
|
import Expression from './Expression.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template {'MustacheTag' | 'RawMustacheTag'} [Type='MustacheTag' | 'RawMustacheTag']
|
||||||
|
* @extends Node<Type>
|
||||||
|
*/
|
||||||
export default class Tag extends Node {
|
export default class Tag extends Node {
|
||||||
type: 'MustacheTag' | 'RawMustacheTag';
|
/** @type {import('./Expression.js').default} */
|
||||||
expression: Expression;
|
expression;
|
||||||
should_cache: boolean;
|
|
||||||
|
|
||||||
|
/** @type {boolean} */
|
||||||
|
should_cache;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {any} component
|
||||||
|
* @param {any} parent
|
||||||
|
* @param {any} scope
|
||||||
|
* @param {any} info
|
||||||
|
*/
|
||||||
constructor(component, parent, scope, info) {
|
constructor(component, parent, scope, info) {
|
||||||
super(component, parent, scope, info);
|
super(component, parent, scope, info);
|
||||||
component.tags.push(this);
|
component.tags.push(this);
|
||||||
this.cannot_use_innerhtml();
|
this.cannot_use_innerhtml();
|
||||||
|
|
||||||
this.expression = new Expression(component, this, scope, info.expression);
|
this.expression = new Expression(component, this, scope, info.expression);
|
||||||
|
|
||||||
this.should_cache =
|
this.should_cache =
|
||||||
info.expression.type !== 'Identifier' ||
|
info.expression.type !== 'Identifier' ||
|
||||||
(this.expression.dependencies.size && scope.names.has(info.expression.name));
|
(this.expression.dependencies.size && scope.names.has(info.expression.name));
|
@ -0,0 +1,82 @@
|
|||||||
|
export default class TemplateScope {
|
||||||
|
/**
|
||||||
|
* @typedef {import('../EachBlock').default
|
||||||
|
* | import('../ThenBlock').default
|
||||||
|
* | import('../CatchBlock').default
|
||||||
|
* | import('../InlineComponent').default
|
||||||
|
* | import('../Element').default
|
||||||
|
* | import('../SlotTemplate').default
|
||||||
|
* | import('../ConstTag').default} NodeWithScope
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @type {Set<string>} */
|
||||||
|
names;
|
||||||
|
|
||||||
|
/** @type {Map<string, Set<string>>} */
|
||||||
|
dependencies_for_name;
|
||||||
|
|
||||||
|
/** @type {Map<string, NodeWithScope>} */
|
||||||
|
owners = new Map();
|
||||||
|
|
||||||
|
/** @type {TemplateScope} */
|
||||||
|
parent;
|
||||||
|
|
||||||
|
/** @param {TemplateScope} [parent] undefined */
|
||||||
|
constructor(parent) {
|
||||||
|
this.parent = parent;
|
||||||
|
this.names = new Set(parent ? parent.names : []);
|
||||||
|
this.dependencies_for_name = new Map(parent ? parent.dependencies_for_name : []);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {any} name
|
||||||
|
* @param {Set<string>} dependencies
|
||||||
|
* @param {any} owner
|
||||||
|
*/
|
||||||
|
add(name, dependencies, owner) {
|
||||||
|
this.names.add(name);
|
||||||
|
this.dependencies_for_name.set(name, dependencies);
|
||||||
|
this.owners.set(name, owner);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
child() {
|
||||||
|
const child = new TemplateScope(this);
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {string} name */
|
||||||
|
is_top_level(name) {
|
||||||
|
return !this.parent || (!this.names.has(name) && this.parent.is_top_level(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} name
|
||||||
|
* @returns {NodeWithScope}
|
||||||
|
*/
|
||||||
|
get_owner(name) {
|
||||||
|
return this.owners.get(name) || (this.parent && this.parent.get_owner(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {string} name */
|
||||||
|
is_let(name) {
|
||||||
|
const owner = this.get_owner(name);
|
||||||
|
return (
|
||||||
|
owner &&
|
||||||
|
(owner.type === 'Element' ||
|
||||||
|
owner.type === 'InlineComponent' ||
|
||||||
|
owner.type === 'SlotTemplate')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {string} name */
|
||||||
|
is_await(name) {
|
||||||
|
const owner = this.get_owner(name);
|
||||||
|
return owner && (owner.type === 'ThenBlock' || owner.type === 'CatchBlock');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {string} name */
|
||||||
|
is_const(name) {
|
||||||
|
const owner = this.get_owner(name);
|
||||||
|
return owner && owner.type === 'ConstTag';
|
||||||
|
}
|
||||||
|
}
|
@ -1,69 +0,0 @@
|
|||||||
import EachBlock from '../EachBlock';
|
|
||||||
import ThenBlock from '../ThenBlock';
|
|
||||||
import CatchBlock from '../CatchBlock';
|
|
||||||
import InlineComponent from '../InlineComponent';
|
|
||||||
import Element from '../Element';
|
|
||||||
import SlotTemplate from '../SlotTemplate';
|
|
||||||
import ConstTag from '../ConstTag';
|
|
||||||
|
|
||||||
type NodeWithScope =
|
|
||||||
| EachBlock
|
|
||||||
| ThenBlock
|
|
||||||
| CatchBlock
|
|
||||||
| InlineComponent
|
|
||||||
| Element
|
|
||||||
| SlotTemplate
|
|
||||||
| ConstTag;
|
|
||||||
|
|
||||||
export default class TemplateScope {
|
|
||||||
names: Set<string>;
|
|
||||||
dependencies_for_name: Map<string, Set<string>>;
|
|
||||||
owners: Map<string, NodeWithScope> = new Map();
|
|
||||||
parent?: TemplateScope;
|
|
||||||
|
|
||||||
constructor(parent?: TemplateScope) {
|
|
||||||
this.parent = parent;
|
|
||||||
this.names = new Set(parent ? parent.names : []);
|
|
||||||
this.dependencies_for_name = new Map(parent ? parent.dependencies_for_name : []);
|
|
||||||
}
|
|
||||||
|
|
||||||
add(name, dependencies: Set<string>, owner) {
|
|
||||||
this.names.add(name);
|
|
||||||
this.dependencies_for_name.set(name, dependencies);
|
|
||||||
this.owners.set(name, owner);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
child() {
|
|
||||||
const child = new TemplateScope(this);
|
|
||||||
return child;
|
|
||||||
}
|
|
||||||
|
|
||||||
is_top_level(name: string) {
|
|
||||||
return !this.parent || (!this.names.has(name) && this.parent.is_top_level(name));
|
|
||||||
}
|
|
||||||
|
|
||||||
get_owner(name: string): NodeWithScope {
|
|
||||||
return this.owners.get(name) || (this.parent && this.parent.get_owner(name));
|
|
||||||
}
|
|
||||||
|
|
||||||
is_let(name: string) {
|
|
||||||
const owner = this.get_owner(name);
|
|
||||||
return (
|
|
||||||
owner &&
|
|
||||||
(owner.type === 'Element' ||
|
|
||||||
owner.type === 'InlineComponent' ||
|
|
||||||
owner.type === 'SlotTemplate')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
is_await(name: string) {
|
|
||||||
const owner = this.get_owner(name);
|
|
||||||
return owner && (owner.type === 'ThenBlock' || owner.type === 'CatchBlock');
|
|
||||||
}
|
|
||||||
|
|
||||||
is_const(name: string) {
|
|
||||||
const owner = this.get_owner(name);
|
|
||||||
return owner && owner.type === 'ConstTag';
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,115 @@
|
|||||||
|
import ConstTag from '../ConstTag.js';
|
||||||
|
import map_children from './map_children.js';
|
||||||
|
import check_graph_for_cycles from '../../utils/check_graph_for_cycles.js';
|
||||||
|
import compiler_errors from '../../compiler_errors.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('../../../interfaces.js').TemplateNode[]} children
|
||||||
|
* @param {import('../../Component.js').default} component
|
||||||
|
* @param {import('../interfaces.js').INodeAllowConstTag} node
|
||||||
|
* @param {import('../interfaces.js').INode} parent
|
||||||
|
* @returns {[ConstTag[], Array<Exclude<import('../interfaces.js').INode, ConstTag>>]}
|
||||||
|
*/
|
||||||
|
export default function get_const_tags(children, component, node, parent) {
|
||||||
|
/** @type {import('../../../interfaces.js').ConstTag[]} */
|
||||||
|
const const_tags = [];
|
||||||
|
|
||||||
|
/** @type {Array<Exclude<import('../../../interfaces.js').TemplateNode, import('../../../interfaces.js').ConstTag>>} */
|
||||||
|
const others = [];
|
||||||
|
for (const child of children) {
|
||||||
|
if (child.type === 'ConstTag') {
|
||||||
|
const_tags.push(/** @type {import('../../../interfaces.js').ConstTag} */ (child));
|
||||||
|
} else {
|
||||||
|
others.push(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const consts_nodes = const_tags.map(
|
||||||
|
/** @param {any} tag */ (tag) => new ConstTag(component, node, node.scope, tag)
|
||||||
|
);
|
||||||
|
const sorted_consts_nodes = sort_consts_nodes(consts_nodes, component);
|
||||||
|
sorted_consts_nodes.forEach(/** @param {any} node */ (node) => node.parse_expression());
|
||||||
|
const children_nodes = map_children(component, parent, node.scope, others);
|
||||||
|
return [
|
||||||
|
sorted_consts_nodes,
|
||||||
|
/** @type {Array<Exclude<import('../interfaces.js').INode, ConstTag>>} */ (children_nodes)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {ConstTag[]} consts_nodes
|
||||||
|
* @param {import('../../Component.js').default} component
|
||||||
|
*/
|
||||||
|
function sort_consts_nodes(consts_nodes, component) {
|
||||||
|
/** @typedef {{ assignees: Set<string>; dependencies: Set<string>; node: ConstTag; }} ConstNode */
|
||||||
|
|
||||||
|
/** @type {ConstNode[]} */
|
||||||
|
const sorted_consts_nodes = [];
|
||||||
|
|
||||||
|
/** @type {ConstNode[]} */
|
||||||
|
const unsorted_consts_nodes = consts_nodes.map(
|
||||||
|
/** @param {any} node */ (node) => {
|
||||||
|
return {
|
||||||
|
assignees: node.assignees,
|
||||||
|
dependencies: node.dependencies,
|
||||||
|
node
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const lookup = new Map();
|
||||||
|
unsorted_consts_nodes.forEach(
|
||||||
|
/** @param {any} node */ (node) => {
|
||||||
|
node.assignees.forEach(
|
||||||
|
/** @param {any} name */ (name) => {
|
||||||
|
if (!lookup.has(name)) {
|
||||||
|
lookup.set(name, []);
|
||||||
|
}
|
||||||
|
lookup.get(name).push(node);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const cycle = check_graph_for_cycles(
|
||||||
|
unsorted_consts_nodes.reduce(
|
||||||
|
/**
|
||||||
|
* @param {any} acc
|
||||||
|
* @param {any} node
|
||||||
|
*/ (acc, node) => {
|
||||||
|
node.assignees.forEach(
|
||||||
|
/** @param {any} v */ (v) => {
|
||||||
|
node.dependencies.forEach(
|
||||||
|
/** @param {any} w */ (w) => {
|
||||||
|
if (!node.assignees.has(w)) {
|
||||||
|
acc.push([v, w]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if (cycle && cycle.length) {
|
||||||
|
const nodeList = lookup.get(cycle[0]);
|
||||||
|
const node = nodeList[0];
|
||||||
|
component.error(node.node, compiler_errors.cyclical_const_tags(cycle));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {ConstNode} node */
|
||||||
|
const add_node = (node) => {
|
||||||
|
if (sorted_consts_nodes.includes(node)) return;
|
||||||
|
node.dependencies.forEach(
|
||||||
|
/** @param {any} name */ (name) => {
|
||||||
|
if (node.assignees.has(name)) return;
|
||||||
|
const earlier_nodes = lookup.get(name);
|
||||||
|
if (earlier_nodes) {
|
||||||
|
earlier_nodes.forEach(add_node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
sorted_consts_nodes.push(node);
|
||||||
|
};
|
||||||
|
unsorted_consts_nodes.forEach(add_node);
|
||||||
|
return sorted_consts_nodes.map(/** @param {any} node */ (node) => node.node);
|
||||||
|
}
|
@ -1,98 +0,0 @@
|
|||||||
import { TemplateNode, ConstTag as ConstTagType } from '../../../interfaces';
|
|
||||||
import Component from '../../Component';
|
|
||||||
import ConstTag from '../ConstTag';
|
|
||||||
import map_children from './map_children';
|
|
||||||
import { INodeAllowConstTag, INode } from '../interfaces';
|
|
||||||
import check_graph_for_cycles from '../../utils/check_graph_for_cycles';
|
|
||||||
import compiler_errors from '../../compiler_errors';
|
|
||||||
|
|
||||||
export default function get_const_tags(
|
|
||||||
children: TemplateNode[],
|
|
||||||
component: Component,
|
|
||||||
node: INodeAllowConstTag,
|
|
||||||
parent: INode
|
|
||||||
): [ConstTag[], Array<Exclude<INode, ConstTag>>] {
|
|
||||||
const const_tags: ConstTagType[] = [];
|
|
||||||
const others: Array<Exclude<TemplateNode, ConstTagType>> = [];
|
|
||||||
|
|
||||||
for (const child of children) {
|
|
||||||
if (child.type === 'ConstTag') {
|
|
||||||
const_tags.push(child as ConstTagType);
|
|
||||||
} else {
|
|
||||||
others.push(child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const consts_nodes = const_tags.map((tag) => new ConstTag(component, node, node.scope, tag));
|
|
||||||
const sorted_consts_nodes = sort_consts_nodes(consts_nodes, component);
|
|
||||||
sorted_consts_nodes.forEach((node) => node.parse_expression());
|
|
||||||
|
|
||||||
const children_nodes = map_children(component, parent, node.scope, others);
|
|
||||||
|
|
||||||
return [sorted_consts_nodes, children_nodes as Array<Exclude<INode, ConstTag>>];
|
|
||||||
}
|
|
||||||
|
|
||||||
function sort_consts_nodes(consts_nodes: ConstTag[], component: Component) {
|
|
||||||
type ConstNode = {
|
|
||||||
assignees: Set<string>;
|
|
||||||
dependencies: Set<string>;
|
|
||||||
node: ConstTag;
|
|
||||||
};
|
|
||||||
const sorted_consts_nodes: ConstNode[] = [];
|
|
||||||
|
|
||||||
const unsorted_consts_nodes: ConstNode[] = consts_nodes.map((node) => {
|
|
||||||
return {
|
|
||||||
assignees: node.assignees,
|
|
||||||
dependencies: node.dependencies,
|
|
||||||
node
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const lookup = new Map();
|
|
||||||
|
|
||||||
unsorted_consts_nodes.forEach((node) => {
|
|
||||||
node.assignees.forEach((name) => {
|
|
||||||
if (!lookup.has(name)) {
|
|
||||||
lookup.set(name, []);
|
|
||||||
}
|
|
||||||
lookup.get(name).push(node);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const cycle = check_graph_for_cycles(
|
|
||||||
unsorted_consts_nodes.reduce((acc, node) => {
|
|
||||||
node.assignees.forEach((v) => {
|
|
||||||
node.dependencies.forEach((w) => {
|
|
||||||
if (!node.assignees.has(w)) {
|
|
||||||
acc.push([v, w]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return acc;
|
|
||||||
}, [])
|
|
||||||
);
|
|
||||||
|
|
||||||
if (cycle && cycle.length) {
|
|
||||||
const nodeList = lookup.get(cycle[0]);
|
|
||||||
const node = nodeList[0];
|
|
||||||
component.error(node.node, compiler_errors.cyclical_const_tags(cycle));
|
|
||||||
}
|
|
||||||
|
|
||||||
const add_node = (node: ConstNode) => {
|
|
||||||
if (sorted_consts_nodes.includes(node)) return;
|
|
||||||
|
|
||||||
node.dependencies.forEach((name) => {
|
|
||||||
if (node.assignees.has(name)) return;
|
|
||||||
const earlier_nodes = lookup.get(name);
|
|
||||||
if (earlier_nodes) {
|
|
||||||
earlier_nodes.forEach(add_node);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
sorted_consts_nodes.push(node);
|
|
||||||
};
|
|
||||||
|
|
||||||
unsorted_consts_nodes.forEach(add_node);
|
|
||||||
|
|
||||||
return sorted_consts_nodes.map((node) => node.node);
|
|
||||||
}
|
|
@ -1,18 +1,17 @@
|
|||||||
import Component from '../../Component';
|
import { is_reserved_keyword } from '../../utils/reserved_keywords.js';
|
||||||
import TemplateScope from './TemplateScope';
|
|
||||||
import { is_reserved_keyword } from '../../utils/reserved_keywords';
|
|
||||||
|
|
||||||
export default function is_contextual(component: Component, scope: TemplateScope, name: string) {
|
/**
|
||||||
|
* @param {import('../../Component.js').default} component
|
||||||
|
* @param {import('./TemplateScope.js').default} scope
|
||||||
|
* @param {string} name
|
||||||
|
*/
|
||||||
|
export default function is_contextual(component, scope, name) {
|
||||||
if (is_reserved_keyword(name)) return true;
|
if (is_reserved_keyword(name)) return true;
|
||||||
|
|
||||||
// if it's a name below root scope, it's contextual
|
// if it's a name below root scope, it's contextual
|
||||||
if (!scope.is_top_level(name)) return true;
|
if (!scope.is_top_level(name)) return true;
|
||||||
|
|
||||||
const variable = component.var_lookup.get(name);
|
const variable = component.var_lookup.get(name);
|
||||||
|
|
||||||
// hoistables, module declarations, and imports are non-contextual
|
// hoistables, module declarations, and imports are non-contextual
|
||||||
if (!variable || variable.hoistable) return false;
|
if (!variable || variable.hoistable) return false;
|
||||||
|
|
||||||
// assume contextual
|
// assume contextual
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
@ -0,0 +1,98 @@
|
|||||||
|
import AwaitBlock from '../AwaitBlock.js';
|
||||||
|
import Body from '../Body.js';
|
||||||
|
import ConstTag from '../ConstTag.js';
|
||||||
|
import Comment from '../Comment.js';
|
||||||
|
import EachBlock from '../EachBlock.js';
|
||||||
|
import Document from '../Document.js';
|
||||||
|
import Element from '../Element.js';
|
||||||
|
import Head from '../Head.js';
|
||||||
|
import IfBlock from '../IfBlock.js';
|
||||||
|
import InlineComponent from '../InlineComponent.js';
|
||||||
|
import KeyBlock from '../KeyBlock.js';
|
||||||
|
import MustacheTag from '../MustacheTag.js';
|
||||||
|
import Options from '../Options.js';
|
||||||
|
import RawMustacheTag from '../RawMustacheTag.js';
|
||||||
|
import DebugTag from '../DebugTag.js';
|
||||||
|
import Slot from '../Slot.js';
|
||||||
|
import SlotTemplate from '../SlotTemplate.js';
|
||||||
|
import Text from '../Text.js';
|
||||||
|
import Title from '../Title.js';
|
||||||
|
import Window from '../Window.js';
|
||||||
|
import { push_array } from '../../../utils/push_array.js';
|
||||||
|
|
||||||
|
/** @typedef {ReturnType<typeof map_children>} Children */
|
||||||
|
|
||||||
|
/** @param {any} type */
|
||||||
|
function get_constructor(type) {
|
||||||
|
switch (type) {
|
||||||
|
case 'AwaitBlock':
|
||||||
|
return AwaitBlock;
|
||||||
|
case 'Body':
|
||||||
|
return Body;
|
||||||
|
case 'Comment':
|
||||||
|
return Comment;
|
||||||
|
case 'ConstTag':
|
||||||
|
return ConstTag;
|
||||||
|
case 'Document':
|
||||||
|
return Document;
|
||||||
|
case 'EachBlock':
|
||||||
|
return EachBlock;
|
||||||
|
case 'Element':
|
||||||
|
return Element;
|
||||||
|
case 'Head':
|
||||||
|
return Head;
|
||||||
|
case 'IfBlock':
|
||||||
|
return IfBlock;
|
||||||
|
case 'InlineComponent':
|
||||||
|
return InlineComponent;
|
||||||
|
case 'KeyBlock':
|
||||||
|
return KeyBlock;
|
||||||
|
case 'MustacheTag':
|
||||||
|
return MustacheTag;
|
||||||
|
case 'Options':
|
||||||
|
return Options;
|
||||||
|
case 'RawMustacheTag':
|
||||||
|
return RawMustacheTag;
|
||||||
|
case 'DebugTag':
|
||||||
|
return DebugTag;
|
||||||
|
case 'Slot':
|
||||||
|
return Slot;
|
||||||
|
case 'SlotTemplate':
|
||||||
|
return SlotTemplate;
|
||||||
|
case 'Text':
|
||||||
|
return Text;
|
||||||
|
case 'Title':
|
||||||
|
return Title;
|
||||||
|
case 'Window':
|
||||||
|
return Window;
|
||||||
|
default:
|
||||||
|
throw new Error(`Not implemented: ${type}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {any} component
|
||||||
|
* @param {any} parent
|
||||||
|
* @param {any} scope
|
||||||
|
* @param {import('../../../interfaces.js').TemplateNode[]} children
|
||||||
|
*/
|
||||||
|
export default function map_children(component, parent, scope, children) {
|
||||||
|
let last = null;
|
||||||
|
let ignores = [];
|
||||||
|
return children.map(
|
||||||
|
/** @param {any} child */ (child) => {
|
||||||
|
const constructor = get_constructor(child.type);
|
||||||
|
const use_ignores = child.type !== 'Text' && child.type !== 'Comment' && ignores.length;
|
||||||
|
if (use_ignores) component.push_ignores(ignores);
|
||||||
|
const node = new constructor(component, parent, scope, child);
|
||||||
|
if (use_ignores) component.pop_ignores(), (ignores = []);
|
||||||
|
if (node.type === 'Comment' && node.ignores.length) {
|
||||||
|
push_array(ignores, node.ignores);
|
||||||
|
}
|
||||||
|
if (last) last.next = node;
|
||||||
|
node.prev = last;
|
||||||
|
last = node;
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue