Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

handle rest elements in computed properties #1628

Merged
merged 1 commit into from
Aug 4, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions src/compile/Compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ import { Node, GenerateOptions, ShorthandImport, Ast, CompileOptions, CustomElem

interface Computation {
key: string;
deps: string[]
deps: string[];
hasRestParam: boolean;
}

function detectIndentation(str: string) {
Expand Down Expand Up @@ -436,7 +437,6 @@ export default class Compiler {
code,
source,
computations,
methods,
templateProperties,
imports
} = this;
Expand Down Expand Up @@ -588,15 +588,20 @@ export default class Compiler {

const param = value.params[0];

if (param.type === 'ObjectPattern') {
const hasRestParam = (
param.properties &&
param.properties.some(prop => prop.type === 'RestElement')
);

if (param.type !== 'ObjectPattern' || hasRestParam) {
fullStateComputations.push({ key, deps: null, hasRestParam });
} else {
const deps = param.properties.map(prop => prop.key.name);

deps.forEach(dep => {
this.expectedProperties.add(dep);
});
dependencies.set(key, deps);
} else {
fullStateComputations.push({ key, deps: null })
}
});

Expand All @@ -611,7 +616,7 @@ export default class Compiler {
const deps = dependencies.get(key);
deps.forEach(visit);

computations.push({ key, deps });
computations.push({ key, deps, hasRestParam: false });

const prop = templateProperties.computed.value.properties.find((prop: Node) => getName(prop.key) === key);
};
Expand Down
6 changes: 4 additions & 2 deletions src/compile/dom/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export default function dom(
const computationDeps = new Set();

if (computations.length) {
computations.forEach(({ key, deps }) => {
computations.forEach(({ key, deps, hasRestParam }) => {
if (target.readonly.has(key)) {
// <svelte:window> bindings
throw new Error(
Expand All @@ -89,8 +89,10 @@ export default function dom(
} else {
// computed property depends on entire state object —
// these must go at the end
const arg = hasRestParam ? `@exclude(state, "${key}")` : `state`;

computationBuilder.addLine(
`if (this._differs(state.${key}, (state.${key} = %computed-${key}(state)))) changed.${key} = true;`
`if (this._differs(state.${key}, (state.${key} = %computed-${key}(${arg})))) changed.${key} = true;`
);
}
});
Expand Down
6 changes: 6 additions & 0 deletions src/shared/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,10 @@ export function addLoc(element, file, line, column, char) {
element.__svelte_meta = {
loc: { file, line, column, char }
};
}

export function exclude(src, prop) {
const tar = {};
for (const k in src) k === prop || (tar[k] = src[k]);
return tar;
}
6 changes: 5 additions & 1 deletion src/utils/annotateWithScopes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,11 @@ const extractors = {

ObjectPattern(names: string[], param: Node) {
param.properties.forEach((prop: Node) => {
extractors[prop.value.type](names, prop.value);
if (prop.type === 'RestElement') {
names.push(prop.argument.name);
} else {
extractors[prop.value.type](names, prop.value);
}
});
},

Expand Down
27 changes: 27 additions & 0 deletions test/runtime/samples/object-rest/_config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
export default {
skip: +/v(\d+)/.exec(process.version)[1] < 8,

html: `
<pre>{"wanted":2}</pre>
`,

test(assert, component, target) {
component.set({
unwanted: 3,
wanted: 4
});

assert.htmlEqual(target.innerHTML, `
<pre>{"wanted":4}</pre>
`);

component.set({
unwanted: 5,
wanted: 6
});

assert.htmlEqual(target.innerHTML, `
<pre>{"wanted":6}</pre>
`);
}
};
18 changes: 18 additions & 0 deletions test/runtime/samples/object-rest/main.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<pre>{JSON.stringify(props)}</pre>
<script>
export default {
data: () => ({
unwanted: 1,
wanted: 2
}),

helpers: {
// prevent this being mixed in with data
JSON
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hrm I'm not thrilled with having to do this ... Is it practical to change how whitelisted globals work so that they don't get assigned into the component's data? I actually wasn't aware or had forgotten that this was the case.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a bit annoying, yeah, but is by far the easiest way to make globals available. The alternative would be to more cleanly separate state and the root ctx object:

// on init
const base = { JSON };

// on each change
const ctx = assign({}, base, newState);
this._recompute(changed, ctx);

That might be necessary anyway at some point, to work around some other quirks relating to merging store with non-store data, and also this.get({ computed: false }) etc. But I think it's orthogonal to this issue

},

computed: {
props: ({ unwanted, ...x }) => x
}
};
</script>