From 7c1e6a6ce7cce68f41a7628d6b59340673e05046 Mon Sep 17 00:00:00 2001 From: halfnelson Date: Sun, 25 Oct 2020 22:35:10 +1000 Subject: [PATCH 1/8] refactor sourcemap and preprocessor tests (#5583) Co-authored-by: Milan Hauth --- .gitignore | 4 - package.json | 2 +- test/preprocess/index.ts | 9 +- test/preprocess/samples/filename/_config.js | 4 +- test/sourcemaps/index.ts | 108 ++++++++++++------ test/sourcemaps/samples/basic/test.js | 12 +- .../samples/binding-shorthand.skip/test.js | 13 ++- test/sourcemaps/samples/binding/test.js | 12 +- test/sourcemaps/samples/css/test.js | 10 +- test/sourcemaps/samples/each-block/test.js | 10 +- .../samples/script-after-comment/test.js | 10 +- test/sourcemaps/samples/script/test.js | 8 +- .../samples/static-no-script/test.js | 12 +- test/sourcemaps/samples/two-scripts/test.js | 8 +- 14 files changed, 133 insertions(+), 89 deletions(-) diff --git a/.gitignore b/.gitignore index f7fac04eba..a471c3aaa2 100644 --- a/.gitignore +++ b/.gitignore @@ -18,10 +18,6 @@ node_modules /coverage/ /coverage.lcov /test/*/samples/_ -/test/sourcemaps/samples/*/output.js -/test/sourcemaps/samples/*/output.js.map -/test/sourcemaps/samples/*/output.css -/test/sourcemaps/samples/*/output.css.map /yarn-error.log _actual*.* _output diff --git a/package.json b/package.json index 6d25f68a25..c2b15d50d0 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "posttest": "agadoo internal/index.mjs", "prepublishOnly": "npm run lint && PUBLISH=true npm test", "tsd": "tsc -p src/compiler --emitDeclarationOnly && tsc -p src/runtime --emitDeclarationOnly", - "lint": "eslint '{src,test}/**/*.{ts,js}'" + "lint": "eslint \"{src,test}/**/*.{ts,js}\"" }, "repository": { "type": "git", diff --git a/test/preprocess/index.ts b/test/preprocess/index.ts index abb45012ce..60d3acbabb 100644 --- a/test/preprocess/index.ts +++ b/test/preprocess/index.ts @@ -8,16 +8,21 @@ describe('preprocess', () => { const config = loadConfig(`${__dirname}/samples/${dir}/_config.js`); const solo = config.solo || /\.solo/.test(dir); + const skip = config.skip || /\.skip/.test(dir); if (solo && process.env.CI) { throw new Error('Forgot to remove `solo: true` from test'); } - (config.skip ? it.skip : solo ? it.only : it)(dir, async () => { + (skip ? it.skip : solo ? it.only : it)(dir, async () => { const input = fs.readFileSync(`${__dirname}/samples/${dir}/input.svelte`, 'utf-8'); const expected = fs.readFileSync(`${__dirname}/samples/${dir}/output.svelte`, 'utf-8'); - const result = await svelte.preprocess(input, config.preprocess); + const result = await svelte.preprocess( + input, + config.preprocess || {}, + config.options || { filename: 'input.svelte' } + ); fs.writeFileSync(`${__dirname}/samples/${dir}/_actual.html`, result.code); assert.equal(result.code, expected); diff --git a/test/preprocess/samples/filename/_config.js b/test/preprocess/samples/filename/_config.js index 2656a835d7..161f084730 100644 --- a/test/preprocess/samples/filename/_config.js +++ b/test/preprocess/samples/filename/_config.js @@ -1,6 +1,5 @@ export default { preprocess: { - filename: 'file.svelte', markup: ({ content, filename }) => { return { code: content.replace('__MARKUP_FILENAME__', filename) @@ -16,5 +15,8 @@ export default { code: content.replace('__SCRIPT_FILENAME__', filename) }; } + }, + options: { + filename: 'file.svelte' } }; diff --git a/test/sourcemaps/index.ts b/test/sourcemaps/index.ts index 2302685bae..7659948744 100644 --- a/test/sourcemaps/index.ts +++ b/test/sourcemaps/index.ts @@ -1,73 +1,113 @@ import * as fs from 'fs'; import * as path from 'path'; import * as assert from 'assert'; -import { svelte } from '../helpers'; -import { SourceMapConsumer } from 'source-map'; +import { loadConfig, svelte } from '../helpers'; +// keep source-map at version 0.7.x +// https://github.com/mozilla/source-map/issues/400 import { getLocator } from 'locate-character'; +import { SourceMapConsumer } from 'source-map'; + describe('sourcemaps', () => { fs.readdirSync(`${__dirname}/samples`).forEach(dir => { if (dir[0] === '.') return; + const config = loadConfig(`${__dirname}/samples/${dir}/_config.js`); + // add .solo to a sample directory name to only run that test - const solo = /\.solo/.test(dir); - const skip = /\.skip/.test(dir); + const solo = config.solo || /\.solo/.test(dir); + const skip = config.skip || /\.skip/.test(dir); if (solo && process.env.CI) { throw new Error('Forgot to remove `solo: true` from test'); } (solo ? it.only : skip ? it.skip : it)(dir, async () => { - const filename = path.resolve( - `${__dirname}/samples/${dir}/input.svelte` - ); - const outputFilename = path.resolve( - `${__dirname}/samples/${dir}/output` - ); + const { test } = require(`./samples/${dir}/test.js`); + const inputFile = path.resolve(`${__dirname}/samples/${dir}/input.svelte`); + const outputName = '_actual'; + const outputBase = path.resolve(`${__dirname}/samples/${dir}/${outputName}`); + + const inputCode = fs.readFileSync(inputFile, 'utf-8'); + const input = { + code: inputCode, + locate: getLocator(inputCode) + }; - const input = fs.readFileSync(filename, 'utf-8').replace(/\s+$/, ''); - const { js, css } = svelte.compile(input, { - filename, - outputFilename: `${outputFilename}.js`, - cssOutputFilename: `${outputFilename}.css` + const preprocessed = await svelte.preprocess( + input.code, + config.preprocess || {}, + { + filename: 'input.svelte' + } + ); + + const { js, css } = svelte.compile( + preprocessed.code, { + filename: 'input.svelte', + // filenames for sourcemaps + outputFilename: `${outputName}.js`, + cssOutputFilename: `${outputName}.css` }); - const _code = js.code.replace(/Svelte v\d+\.\d+\.\d+/, match => match.replace(/\d/g, 'x')); + js.code = js.code.replace( + /generated by Svelte v\d+\.\d+\.\d+/, + match => match.replace(/\d/g, 'x') + ); + fs.writeFileSync(`${outputBase}.svelte`, preprocessed.code); + if (preprocessed.map) { + fs.writeFileSync( + `${outputBase}.svelte.map`, + // TODO encode mappings for output - svelte.preprocess returns decoded mappings + JSON.stringify(preprocessed.map, null, 2) + ); + } fs.writeFileSync( - `${outputFilename}.js`, - `${_code}\n//# sourceMappingURL=output.js.map` + `${outputBase}.js`, + `${js.code}\n//# sourceMappingURL=${outputName}.js.map` ); fs.writeFileSync( - `${outputFilename}.js.map`, - JSON.stringify(js.map, null, ' ') + `${outputBase}.js.map`, + JSON.stringify(js.map, null, 2) ); - if (css.code) { fs.writeFileSync( - `${outputFilename}.css`, - `${css.code}\n/*# sourceMappingURL=output.css.map */` + `${outputBase}.css`, + `${css.code}\n/*# sourceMappingURL=${outputName}.css.map */` ); fs.writeFileSync( - `${outputFilename}.css.map`, + `${outputBase}.css.map`, JSON.stringify(css.map, null, ' ') ); } - assert.deepEqual(js.map.sources, ['input.svelte']); - if (css.map) assert.deepEqual(css.map.sources, ['input.svelte']); - - const { test } = require(`./samples/${dir}/test.js`); + assert.deepEqual( + js.map.sources.slice().sort(), + (config.js_map_sources || ['input.svelte']).sort() + ); + if (css.map) { + assert.deepEqual( + css.map.sources.slice().sort(), + (config.css_map_sources || ['input.svelte']).sort() + ); + } - const locateInSource = getLocator(input); + // use locate_1 with mapConsumer: + // lines are one-based, columns are zero-based - const smc = await new SourceMapConsumer(js.map); - const locateInGenerated = getLocator(_code); + preprocessed.mapConsumer = preprocessed.map && await new SourceMapConsumer(preprocessed.map); + preprocessed.locate = getLocator(preprocessed.code); + preprocessed.locate_1 = getLocator(preprocessed.code, { offsetLine: 1 }); - const smcCss = css.map && await new SourceMapConsumer(css.map); - const locateInGeneratedCss = getLocator(css.code || ''); + js.mapConsumer = js.map && await new SourceMapConsumer(js.map); + js.locate = getLocator(js.code); + js.locate_1 = getLocator(js.code, { offsetLine: 1 }); - test({ assert, code: _code, map: js.map, smc, smcCss, locateInSource, locateInGenerated, locateInGeneratedCss }); + css.mapConsumer = css.map && await new SourceMapConsumer(css.map); + css.locate = getLocator(css.code || ''); + css.locate_1 = getLocator(css.code || '', { offsetLine: 1 }); + test({ assert, input, preprocessed, js, css }); }); }); }); diff --git a/test/sourcemaps/samples/basic/test.js b/test/sourcemaps/samples/basic/test.js index 7059761674..34c619c128 100644 --- a/test/sourcemaps/samples/basic/test.js +++ b/test/sourcemaps/samples/basic/test.js @@ -1,12 +1,12 @@ -export function test({ assert, smc, locateInSource, locateInGenerated }) { - const expected = locateInSource('foo.bar.baz'); +export function test({ assert, input, js }) { + const expected = input.locate('foo.bar.baz'); let start; let actual; - start = locateInGenerated('ctx[0].bar.baz'); + start = js.locate('ctx[0].bar.baz'); - actual = smc.originalPositionFor({ + actual = js.mapConsumer.originalPositionFor({ line: start.line + 1, column: start.column }); @@ -18,9 +18,9 @@ export function test({ assert, smc, locateInSource, locateInGenerated }) { column: expected.column }); - start = locateInGenerated('ctx[0].bar.baz', start.character + 1); + start = js.locate('ctx[0].bar.baz', start.character + 1); - actual = smc.originalPositionFor({ + actual = js.mapConsumer.originalPositionFor({ line: start.line + 1, column: start.column }); diff --git a/test/sourcemaps/samples/binding-shorthand.skip/test.js b/test/sourcemaps/samples/binding-shorthand.skip/test.js index cdfbbdc091..13ecdbf889 100644 --- a/test/sourcemaps/samples/binding-shorthand.skip/test.js +++ b/test/sourcemaps/samples/binding-shorthand.skip/test.js @@ -1,13 +1,14 @@ -export function test({ assert, smc, locateInSource, locateInGenerated }) { - const expected = locateInSource('potato'); +export function test({ assert, input, js }) { + const expected = input.locate('potato'); let start; - start = locateInGenerated('potato'); - start = locateInGenerated('potato', start.character + 1); - start = locateInGenerated('potato', start.character + 1); // we need the third instance of 'potato' + start = js.locate('potato'); + start = js.locate('potato', start.character + 1); + start = js.locate('potato', start.character + 1); + // we need the third instance of 'potato' - const actual = smc.originalPositionFor({ + const actual = js.mapConsumer.originalPositionFor({ line: start.line + 1, column: start.column }); diff --git a/test/sourcemaps/samples/binding/test.js b/test/sourcemaps/samples/binding/test.js index 31c0e98442..3cb3246e50 100644 --- a/test/sourcemaps/samples/binding/test.js +++ b/test/sourcemaps/samples/binding/test.js @@ -1,12 +1,12 @@ -export function test({ assert, smc, locateInSource, locateInGenerated }) { - const expected = locateInSource('bar.baz'); +export function test({ assert, input, js }) { + const expected = input.locate('bar.baz'); let start; let actual; - start = locateInGenerated('bar.baz'); + start = js.locate('bar.baz'); - actual = smc.originalPositionFor({ + actual = js.mapConsumer.originalPositionFor({ line: start.line + 1, column: start.column }); @@ -18,9 +18,9 @@ export function test({ assert, smc, locateInSource, locateInGenerated }) { column: expected.column }); - start = locateInGenerated('bar.baz', start.character + 1); + start = js.locate('bar.baz', start.character + 1); - actual = smc.originalPositionFor({ + actual = js.mapConsumer.originalPositionFor({ line: start.line + 1, column: start.column }); diff --git a/test/sourcemaps/samples/css/test.js b/test/sourcemaps/samples/css/test.js index 54082bd4ee..1e0dda1dff 100644 --- a/test/sourcemaps/samples/css/test.js +++ b/test/sourcemaps/samples/css/test.js @@ -1,14 +1,14 @@ -export function test({ assert, smcCss, locateInSource, locateInGeneratedCss }) { - const expected = locateInSource( '.foo' ); +export function test({ assert, input, css }) { + const expected = input.locate('.foo'); - const start = locateInGeneratedCss( '.foo' ); + const start = css.locate('.foo'); - const actual = smcCss.originalPositionFor({ + const actual = css.mapConsumer.originalPositionFor({ line: start.line + 1, column: start.column }); - assert.deepEqual( actual, { + assert.deepEqual(actual, { source: 'input.svelte', name: null, line: expected.line + 1, diff --git a/test/sourcemaps/samples/each-block/test.js b/test/sourcemaps/samples/each-block/test.js index 35479986a5..08b37686a4 100644 --- a/test/sourcemaps/samples/each-block/test.js +++ b/test/sourcemaps/samples/each-block/test.js @@ -1,10 +1,10 @@ -export function test({ assert, code, smc, map, locateInSource, locateInGenerated }) { - const startIndex = code.indexOf('create_main_fragment'); +export function test({ assert, input, js }) { + const startIndex = js.code.indexOf('create_main_fragment'); - const expected = locateInSource('each'); - const start = locateInGenerated('length', startIndex); + const expected = input.locate('each'); + const start = js.locate('length', startIndex); - const actual = smc.originalPositionFor({ + const actual = js.mapConsumer.originalPositionFor({ line: start.line + 1, column: start.column }); diff --git a/test/sourcemaps/samples/script-after-comment/test.js b/test/sourcemaps/samples/script-after-comment/test.js index b473970112..06ecc46929 100644 --- a/test/sourcemaps/samples/script-after-comment/test.js +++ b/test/sourcemaps/samples/script-after-comment/test.js @@ -1,13 +1,13 @@ -export function test({ assert, smc, locateInSource, locateInGenerated }) { - const expected = locateInSource( 'assertThisLine' ); - const start = locateInGenerated( 'assertThisLine' ); +export function test({ assert, input, js }) { + const expected = input.locate('assertThisLine'); + const start = js.locate('assertThisLine'); - const actual = smc.originalPositionFor({ + const actual = js.mapConsumer.originalPositionFor({ line: start.line + 1, column: start.column }); - assert.deepEqual( actual, { + assert.deepEqual(actual, { source: 'input.svelte', name: null, line: expected.line + 1, diff --git a/test/sourcemaps/samples/script/test.js b/test/sourcemaps/samples/script/test.js index 73971ef487..e6a91f51e1 100644 --- a/test/sourcemaps/samples/script/test.js +++ b/test/sourcemaps/samples/script/test.js @@ -1,8 +1,8 @@ -export function test({ assert, smc, locateInSource, locateInGenerated }) { - const expected = locateInSource( '42' ); - const start = locateInGenerated( '42' ); +export function test({ assert, input, js }) { + const expected = input.locate('42'); + const start = js.locate('42'); - const actual = smc.originalPositionFor({ + const actual = js.mapConsumer.originalPositionFor({ line: start.line + 1, column: start.column }); diff --git a/test/sourcemaps/samples/static-no-script/test.js b/test/sourcemaps/samples/static-no-script/test.js index a8f4d89ab8..c683c94d6b 100644 --- a/test/sourcemaps/samples/static-no-script/test.js +++ b/test/sourcemaps/samples/static-no-script/test.js @@ -1,9 +1,9 @@ -const fs = require( 'fs' ); -const path = require( 'path' ); +const fs = require('fs'); +const path = require('path'); -export function test({ assert, map }) { - assert.deepEqual( map.sources, [ 'input.svelte' ]); - assert.deepEqual( map.sourcesContent, [ - fs.readFileSync( path.join( __dirname, 'input.svelte' ), 'utf-8' ) +export function test({ assert, js }) { + assert.deepEqual(js.map.sources, ['input.svelte']); + assert.deepEqual(js.map.sourcesContent, [ + fs.readFileSync(path.join(__dirname, 'input.svelte'), 'utf-8') ]); } diff --git a/test/sourcemaps/samples/two-scripts/test.js b/test/sourcemaps/samples/two-scripts/test.js index b473970112..70901af8c9 100644 --- a/test/sourcemaps/samples/two-scripts/test.js +++ b/test/sourcemaps/samples/two-scripts/test.js @@ -1,8 +1,8 @@ -export function test({ assert, smc, locateInSource, locateInGenerated }) { - const expected = locateInSource( 'assertThisLine' ); - const start = locateInGenerated( 'assertThisLine' ); +export function test({ assert, input, js }) { + const expected = input.locate( 'assertThisLine' ); + const start = js.locate( 'assertThisLine' ); - const actual = smc.originalPositionFor({ + const actual = js.mapConsumer.originalPositionFor({ line: start.line + 1, column: start.column }); From 7ea6a2994a2a35e96e47d843250cc61c1314ec39 Mon Sep 17 00:00:00 2001 From: Ben McCann <322311+benmccann@users.noreply.github.com> Date: Mon, 26 Oct 2020 10:25:03 -0700 Subject: [PATCH 2/8] Curly braces linting fixes (#5585) --- src/compiler/compile/Component.ts | 21 ++++++++++++------- src/compiler/compile/nodes/Binding.ts | 20 +++++++++++------- .../render_dom/wrappers/Element/Attribute.ts | 3 ++- src/compiler/parse/index.ts | 3 ++- src/compiler/parse/read/script.ts | 10 +++++---- src/compiler/parse/state/mustache.ts | 10 +++++---- src/compiler/utils/fuzzymatch.ts | 3 ++- src/runtime/motion/spring.ts | 6 ++++-- test/hydration/index.ts | 8 ++++--- 9 files changed, 52 insertions(+), 32 deletions(-) diff --git a/src/compiler/compile/Component.ts b/src/compiler/compile/Component.ts index c16862165c..d2542c9830 100644 --- a/src/compiler/compile/Component.ts +++ b/src/compiler/compile/Component.ts @@ -521,8 +521,7 @@ export default class Component { if (this.hoistable_nodes.has(node)) return false; if (this.reactive_declaration_nodes.has(node)) return false; if (node.type === 'ImportDeclaration') return false; - if (node.type === 'ExportDeclaration' && node.specifiers.length > 0) - return false; + if (node.type === 'ExportDeclaration' && node.specifiers.length > 0) return false; return true; }); } @@ -1038,8 +1037,9 @@ export default class Component { this.vars.find( variable => variable.name === name && variable.module ) - ) + ) { return false; + } return true; }); @@ -1288,8 +1288,9 @@ export default class Component { declaration.dependencies.forEach(name => { if (declaration.assignees.has(name)) return; const earlier_declarations = lookup.get(name); - if (earlier_declarations) + if (earlier_declarations) { earlier_declarations.forEach(add_declaration); + } }); this.reactive_declarations.push(declaration); @@ -1319,8 +1320,9 @@ export default class Component { if (globals.has(name) && node.type !== 'InlineComponent') return; let message = `'${name}' is not defined`; - if (!this.ast.instance) + if (!this.ast.instance) { message += `. Consider adding a + +

name: {name}

+

$$props: {JSON.stringify($$props)}

+

$$restProps: {JSON.stringify($$restProps)}

+ diff --git a/test/custom-elements/samples/$$props/test.js b/test/custom-elements/samples/$$props/test.js new file mode 100644 index 0000000000..94cad86577 --- /dev/null +++ b/test/custom-elements/samples/$$props/test.js @@ -0,0 +1,13 @@ +import * as assert from 'assert'; +import './main.svelte'; + +export default function (target) { + target.innerHTML = ''; + const el = target.querySelector('custom-element'); + + assert.htmlEqual(el.shadowRoot.innerHTML, ` +

name: world

+

$$props: {"name":"world","answer":"42","test":"svelte"}

+

$$restProps: {"answer":"42","test":"svelte"}

+ `); +} diff --git a/test/js/samples/css-shadow-dom-keyframes/expected.js b/test/js/samples/css-shadow-dom-keyframes/expected.js index a0a0ebe021..82a39e5924 100644 --- a/test/js/samples/css-shadow-dom-keyframes/expected.js +++ b/test/js/samples/css-shadow-dom-keyframes/expected.js @@ -1,6 +1,7 @@ /* generated by Svelte vX.Y.Z */ import { SvelteElement, + attribute_to_object, detach, element, init, @@ -34,7 +35,18 @@ class Component extends SvelteElement { constructor(options) { super(); this.shadowRoot.innerHTML = ``; - init(this, { target: this.shadowRoot }, null, create_fragment, safe_not_equal, {}); + + init( + this, + { + target: this.shadowRoot, + props: attribute_to_object(this.attributes) + }, + null, + create_fragment, + safe_not_equal, + {} + ); if (options) { if (options.target) { From 5d7ffdb8a7231a772da6b259a8f2c78a4007c53b Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Fri, 30 Oct 2020 03:15:54 +0800 Subject: [PATCH 8/8] fix function slot props based on context (#5607) --- CHANGELOG.md | 1 + .../compile/nodes/shared/Expression.ts | 65 ++++++++++++++----- .../compile/render_dom/wrappers/EachBlock.ts | 27 ++++---- .../Nested.svelte | 14 ++++ .../_config.js | 36 ++++++++++ .../main.svelte | 8 +++ .../Nested.svelte | 11 ++++ .../_config.js | 19 ++++++ .../main.svelte | 8 +++ .../Inner.svelte | 9 +++ .../Nested.svelte | 8 +++ .../_config.js | 19 ++++++ .../main.svelte | 8 +++ 13 files changed, 202 insertions(+), 31 deletions(-) create mode 100644 test/runtime/samples/component-slot-context-props-each-nested/Nested.svelte create mode 100644 test/runtime/samples/component-slot-context-props-each-nested/_config.js create mode 100644 test/runtime/samples/component-slot-context-props-each-nested/main.svelte create mode 100644 test/runtime/samples/component-slot-context-props-each/Nested.svelte create mode 100644 test/runtime/samples/component-slot-context-props-each/_config.js create mode 100644 test/runtime/samples/component-slot-context-props-each/main.svelte create mode 100644 test/runtime/samples/component-slot-context-props-let/Inner.svelte create mode 100644 test/runtime/samples/component-slot-context-props-let/Nested.svelte create mode 100644 test/runtime/samples/component-slot-context-props-let/_config.js create mode 100644 test/runtime/samples/component-slot-context-props-let/main.svelte diff --git a/CHANGELOG.md b/CHANGELOG.md index cfff198368..4afd7f20e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased * Fix `$$props` and `$$restProps` when compiling to a custom element ([#5482](https://github.com/sveltejs/svelte/issues/5482)) +* Fix function calls in `` props that use contextual values ([#5565](https://github.com/sveltejs/svelte/issues/5565)) * Add `Element` and `Node` to known globals ([#5586](https://github.com/sveltejs/svelte/issues/5586)) ## 3.29.4 diff --git a/src/compiler/compile/nodes/shared/Expression.ts b/src/compiler/compile/nodes/shared/Expression.ts index 027e962e29..ef134f4359 100644 --- a/src/compiler/compile/nodes/shared/Expression.ts +++ b/src/compiler/compile/nodes/shared/Expression.ts @@ -4,7 +4,6 @@ import is_reference from 'is-reference'; import flatten_reference from '../../utils/flatten_reference'; import { create_scopes, Scope, extract_names } from '../../utils/scope'; import { sanitize } from '../../../utils/names'; -import Wrapper from '../../render_dom/wrappers/shared/Wrapper'; import TemplateScope from './TemplateScope'; import get_object from '../../utils/get_object'; import Block from '../../render_dom/Block'; @@ -12,12 +11,12 @@ import is_dynamic from '../../render_dom/wrappers/shared/is_dynamic'; import { b } from 'code-red'; import { invalidate } from '../../render_dom/invalidate'; import { Node, FunctionExpression, Identifier } from 'estree'; -import { TemplateNode } from '../../../interfaces'; +import { INode } from '../interfaces'; import { is_reserved_keyword } from '../../utils/reserved_keywords'; import replace_object from '../../utils/replace_object'; import EachBlock from '../EachBlock'; -type Owner = Wrapper | TemplateNode; +type Owner = INode; export default class Expression { type: 'Expression' = 'Expression'; @@ -37,7 +36,6 @@ export default class Expression { manipulated: Node; - // todo: owner type constructor(component: Component, owner: Owner, template_scope: TemplateScope, info, lazy?: boolean) { // TODO revert to direct property access in prod? Object.defineProperties(this, { @@ -276,10 +274,12 @@ export default class Expression { else { // we need a combo block/init recipe const deps = Array.from(contextual_dependencies); + const function_expression = node as FunctionExpression; - (node as FunctionExpression).params = [ + const has_args = function_expression.params.length > 0; + function_expression.params = [ ...deps.map(name => ({ type: 'Identifier', name } as Identifier)), - ...(node as FunctionExpression).params + ...function_expression.params ]; const context_args = deps.map(name => block.renderer.reference(name)); @@ -291,18 +291,49 @@ export default class Expression { this.replace(id as any); - if ((node as FunctionExpression).params.length > 0) { - declarations.push(b` - function ${id}(...args) { - return ${callee}(${context_args}, ...args); - } - `); + const func_declaration = has_args + ? b`function ${id}(...args) { + return ${callee}(${context_args}, ...args); + }` + : b`function ${id}() { + return ${callee}(${context_args}); + }`; + + if (owner.type === 'Attribute' && owner.parent.name === 'slot') { + const dep_scopes = new Set(deps.map(name => template_scope.get_owner(name))); + // find the nearest scopes + let node: INode = owner.parent; + while (node && !dep_scopes.has(node)) { + node = node.parent; + } + + const func_expression = func_declaration[0]; + + if (node.type === 'InlineComponent') { + // + this.replace(func_expression); + } else { + // {#each}, {#await} + const func_id = component.get_unique_name(id.name + '_func'); + block.renderer.add_to_context(func_id.name, true); + // rename #ctx -> child_ctx; + walk(func_expression, { + enter(node) { + if (node.type === 'Identifier' && node.name === '#ctx') { + node.name = 'child_ctx'; + } + } + }); + // add to get_xxx_context + // child_ctx[x] = function () { ... } + (template_scope.get_owner(deps[0]) as EachBlock).contexts.push({ + key: func_id, + modifier: () => func_expression + }); + this.replace(block.renderer.reference(func_id)); + } } else { - declarations.push(b` - function ${id}() { - return ${callee}(${context_args}); - } - `); + declarations.push(func_declaration); } } diff --git a/src/compiler/compile/render_dom/wrappers/EachBlock.ts b/src/compiler/compile/render_dom/wrappers/EachBlock.ts index b31d2bcba7..a2c20e0074 100644 --- a/src/compiler/compile/render_dom/wrappers/EachBlock.ts +++ b/src/compiler/compile/render_dom/wrappers/EachBlock.ts @@ -196,11 +196,6 @@ export default class EachBlockWrapper extends Wrapper { ? !this.next.is_dom_node() : !parent_node || !this.parent.is_dom_node(); - this.context_props = this.node.contexts.map(prop => b`child_ctx[${renderer.context_lookup.get(prop.key.name).index}] = ${prop.modifier(x`list[i]`)};`); - - if (this.node.has_binding) this.context_props.push(b`child_ctx[${renderer.context_lookup.get(this.vars.each_block_value.name).index}] = list;`); - if (this.node.has_binding || this.node.has_index_binding || this.node.index) this.context_props.push(b`child_ctx[${renderer.context_lookup.get(this.index_name.name).index}] = i;`); - const snippet = this.node.expression.manipulate(block); block.chunks.init.push(b`let ${this.vars.each_block_value} = ${snippet};`); @@ -208,15 +203,6 @@ export default class EachBlockWrapper extends Wrapper { block.chunks.init.push(b`@validate_each_argument(${this.vars.each_block_value});`); } - // TODO which is better — Object.create(array) or array.slice()? - renderer.blocks.push(b` - function ${this.vars.get_each_context}(#ctx, list, i) { - const child_ctx = #ctx.slice(); - ${this.context_props} - return child_ctx; - } - `); - const initial_anchor_node: Identifier = { type: 'Identifier', name: parent_node ? 'null' : '#anchor' }; const initial_mount_node: Identifier = parent_node || { type: 'Identifier', name: '#target' }; const update_anchor_node = needs_anchor @@ -360,6 +346,19 @@ export default class EachBlockWrapper extends Wrapper { if (this.else) { this.else.fragment.render(this.else.block, null, x`#nodes` as Identifier); } + + this.context_props = this.node.contexts.map(prop => b`child_ctx[${renderer.context_lookup.get(prop.key.name).index}] = ${prop.modifier(x`list[i]`)};`); + + if (this.node.has_binding) this.context_props.push(b`child_ctx[${renderer.context_lookup.get(this.vars.each_block_value.name).index}] = list;`); + if (this.node.has_binding || this.node.has_index_binding || this.node.index) this.context_props.push(b`child_ctx[${renderer.context_lookup.get(this.index_name.name).index}] = i;`); + // TODO which is better — Object.create(array) or array.slice()? + renderer.blocks.push(b` + function ${this.vars.get_each_context}(#ctx, list, i) { + const child_ctx = #ctx.slice(); + ${this.context_props} + return child_ctx; + } + `); } render_keyed({ diff --git a/test/runtime/samples/component-slot-context-props-each-nested/Nested.svelte b/test/runtime/samples/component-slot-context-props-each-nested/Nested.svelte new file mode 100644 index 0000000000..fab3ae1890 --- /dev/null +++ b/test/runtime/samples/component-slot-context-props-each-nested/Nested.svelte @@ -0,0 +1,14 @@ + + +{#each items as item (item)} + {#each keys as key (key)} + setKey(key, value, item)} /> + {/each} +{/each} \ No newline at end of file diff --git a/test/runtime/samples/component-slot-context-props-each-nested/_config.js b/test/runtime/samples/component-slot-context-props-each-nested/_config.js new file mode 100644 index 0000000000..869cc555b1 --- /dev/null +++ b/test/runtime/samples/component-slot-context-props-each-nested/_config.js @@ -0,0 +1,36 @@ +export default { + html: ` + + + + + `, + async test({ assert, target, window, component }) { + const [btn1, btn2, btn3, btn4] = target.querySelectorAll('button'); + const click = new window.MouseEvent('click'); + + await btn1.dispatchEvent(click); + assert.deepEqual(component.log, ['setKey(a, value-a-c, c)']); + + await btn2.dispatchEvent(click); + assert.deepEqual(component.log, [ + 'setKey(a, value-a-c, c)', + 'setKey(b, value-b-c, c)' + ]); + + await btn3.dispatchEvent(click); + assert.deepEqual(component.log, [ + 'setKey(a, value-a-c, c)', + 'setKey(b, value-b-c, c)', + 'setKey(a, value-a-d, d)' + ]); + + await btn4.dispatchEvent(click); + assert.deepEqual(component.log, [ + 'setKey(a, value-a-c, c)', + 'setKey(b, value-b-c, c)', + 'setKey(a, value-a-d, d)', + 'setKey(b, value-b-d, d)' + ]); + } +}; diff --git a/test/runtime/samples/component-slot-context-props-each-nested/main.svelte b/test/runtime/samples/component-slot-context-props-each-nested/main.svelte new file mode 100644 index 0000000000..7feb55aaf6 --- /dev/null +++ b/test/runtime/samples/component-slot-context-props-each-nested/main.svelte @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/test/runtime/samples/component-slot-context-props-each/Nested.svelte b/test/runtime/samples/component-slot-context-props-each/Nested.svelte new file mode 100644 index 0000000000..a29ffe0376 --- /dev/null +++ b/test/runtime/samples/component-slot-context-props-each/Nested.svelte @@ -0,0 +1,11 @@ + + +{#each keys as key (key)} + setKey(key, value)} /> +{/each} \ No newline at end of file diff --git a/test/runtime/samples/component-slot-context-props-each/_config.js b/test/runtime/samples/component-slot-context-props-each/_config.js new file mode 100644 index 0000000000..f374e7f827 --- /dev/null +++ b/test/runtime/samples/component-slot-context-props-each/_config.js @@ -0,0 +1,19 @@ +export default { + html: ` + + + `, + async test({ assert, target, window, component }) { + const [btn1, btn2] = target.querySelectorAll('button'); + const click = new window.MouseEvent('click'); + + await btn1.dispatchEvent(click); + assert.deepEqual(component.log, ['setKey(a, value-a)']); + + await btn2.dispatchEvent(click); + assert.deepEqual(component.log, [ + 'setKey(a, value-a)', + 'setKey(b, value-b)' + ]); + } +}; diff --git a/test/runtime/samples/component-slot-context-props-each/main.svelte b/test/runtime/samples/component-slot-context-props-each/main.svelte new file mode 100644 index 0000000000..7dc17b4c95 --- /dev/null +++ b/test/runtime/samples/component-slot-context-props-each/main.svelte @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/test/runtime/samples/component-slot-context-props-let/Inner.svelte b/test/runtime/samples/component-slot-context-props-let/Inner.svelte new file mode 100644 index 0000000000..6beaabbb68 --- /dev/null +++ b/test/runtime/samples/component-slot-context-props-let/Inner.svelte @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/test/runtime/samples/component-slot-context-props-let/Nested.svelte b/test/runtime/samples/component-slot-context-props-let/Nested.svelte new file mode 100644 index 0000000000..97c44c5145 --- /dev/null +++ b/test/runtime/samples/component-slot-context-props-let/Nested.svelte @@ -0,0 +1,8 @@ + + + + set(key, value)} /> + \ No newline at end of file diff --git a/test/runtime/samples/component-slot-context-props-let/_config.js b/test/runtime/samples/component-slot-context-props-let/_config.js new file mode 100644 index 0000000000..f374e7f827 --- /dev/null +++ b/test/runtime/samples/component-slot-context-props-let/_config.js @@ -0,0 +1,19 @@ +export default { + html: ` + + + `, + async test({ assert, target, window, component }) { + const [btn1, btn2] = target.querySelectorAll('button'); + const click = new window.MouseEvent('click'); + + await btn1.dispatchEvent(click); + assert.deepEqual(component.log, ['setKey(a, value-a)']); + + await btn2.dispatchEvent(click); + assert.deepEqual(component.log, [ + 'setKey(a, value-a)', + 'setKey(b, value-b)' + ]); + } +}; diff --git a/test/runtime/samples/component-slot-context-props-let/main.svelte b/test/runtime/samples/component-slot-context-props-let/main.svelte new file mode 100644 index 0000000000..a061f73b3a --- /dev/null +++ b/test/runtime/samples/component-slot-context-props-let/main.svelte @@ -0,0 +1,8 @@ + + + + +