Check against this usage in computed functions

pull/1033/head
Emil Ajdyna 7 years ago
parent c13b6ad6a5
commit d561c93841

@ -0,0 +1,8 @@
import { Node } from '../interfaces';
export default function isThisGetCallExpression(node: Node): boolean {
return node.type === 'CallExpression' &&
node.callee.type === 'MemberExpression' &&
node.callee.object.type === 'ThisExpression' &&
node.callee.property.name === 'get';
}

@ -0,0 +1,21 @@
import { Node } from '../interfaces';
import { walk } from 'estree-walker';
export default function walkThroughTopFunctionScope(body: Node, callback: Function) {
let lexicalDepth = 0;
walk(body, {
enter(node: Node) {
if (/^Function/.test(node.type)) {
lexicalDepth += 1;
} else if (lexicalDepth === 0) {
callback(node)
}
},
leave(node: Node) {
if (/^Function/.test(node.type)) {
lexicalDepth -= 1;
}
},
});
}

@ -2,6 +2,8 @@ import checkForDupes from '../utils/checkForDupes';
import checkForComputedKeys from '../utils/checkForComputedKeys'; import checkForComputedKeys from '../utils/checkForComputedKeys';
import { Validator } from '../../'; import { Validator } from '../../';
import { Node } from '../../../interfaces'; import { Node } from '../../../interfaces';
import walkThroughTopFunctionScope from '../../../utils/walkThroughTopFunctionScope';
import isThisGetCallExpression from '../../../utils/isThisGetCallExpression';
const isFunctionExpression = new Set([ const isFunctionExpression = new Set([
'FunctionExpression', 'FunctionExpression',
@ -27,7 +29,23 @@ export default function computed(validator: Validator, prop: Node) {
); );
} }
const params = computation.value.params; const { body, params } = computation.value;
walkThroughTopFunctionScope(body, (node: Node) => {
if (isThisGetCallExpression(node) && !node.callee.property.computed) {
validator.error(
`Cannot use this.get(...) — it must be passed into the computed function as an argument`,
node.start
);
}
if (node.type === 'ThisExpression') {
validator.error(
`Computed should be pure functions — they do not have access to the component instance and cannot use 'this'. Did you mean to put this in 'methods'?`,
node.start
);
}
});
if (params.length === 0) { if (params.length === 0) {
validator.error( validator.error(

@ -3,6 +3,8 @@ import checkForComputedKeys from '../utils/checkForComputedKeys';
import { walk } from 'estree-walker'; import { walk } from 'estree-walker';
import { Validator } from '../../'; import { Validator } from '../../';
import { Node } from '../../../interfaces'; import { Node } from '../../../interfaces';
import walkThroughTopFunctionScope from '../../../utils/walkThroughTopFunctionScope';
import isThisGetCallExpression from '../../../utils/isThisGetCallExpression';
export default function helpers(validator: Validator, prop: Node) { export default function helpers(validator: Validator, prop: Node) {
if (prop.value.type !== 'ObjectExpression') { if (prop.value.type !== 'ObjectExpression') {
@ -18,45 +20,24 @@ export default function helpers(validator: Validator, prop: Node) {
prop.value.properties.forEach((prop: Node) => { prop.value.properties.forEach((prop: Node) => {
if (!/FunctionExpression/.test(prop.value.type)) return; if (!/FunctionExpression/.test(prop.value.type)) return;
let lexicalDepth = 0;
let usesArguments = false; let usesArguments = false;
walk(prop.value.body, { walkThroughTopFunctionScope(prop.value.body, (node: Node) => {
enter(node: Node) { if (isThisGetCallExpression(node) && !node.callee.property.computed) {
if (/^Function/.test(node.type)) { validator.error(
lexicalDepth += 1; `Cannot use this.get(...) — it must be passed into the helper function as an argument`,
} else if (lexicalDepth === 0) { node.start
// handle special case that's caused some people confusion — using `this.get(...)` instead of passing argument );
// TODO do the same thing for computed values? }
if (
node.type === 'CallExpression' && if (node.type === 'ThisExpression') {
node.callee.type === 'MemberExpression' && validator.error(
node.callee.object.type === 'ThisExpression' && `Helpers should be pure functions — they do not have access to the component instance and cannot use 'this'. Did you mean to put this in 'methods'?`,
node.callee.property.name === 'get' && node.start
!node.callee.property.computed );
) { } else if (node.type === 'Identifier' && node.name === 'arguments') {
validator.error( usesArguments = true;
`Cannot use this.get(...) — it must be passed into the helper function as an argument`, }
node.start
);
}
if (node.type === 'ThisExpression') {
validator.error(
`Helpers should be pure functions — they do not have access to the component instance and cannot use 'this'. Did you mean to put this in 'methods'?`,
node.start
);
} else if (node.type === 'Identifier' && node.name === 'arguments') {
usesArguments = true;
}
}
},
leave(node: Node) {
if (/^Function/.test(node.type)) {
lexicalDepth -= 1;
}
},
}); });
if (prop.value.params.length === 0 && !usesArguments) { if (prop.value.params.length === 0 && !usesArguments) {

@ -0,0 +1,8 @@
[{
"message": "Computed should be pure functions — they do not have access to the component instance and cannot use 'this'. Did you mean to put this in 'methods'?",
"pos": 83,
"loc": {
"line": 7,
"column": 4
}
}]

@ -0,0 +1,11 @@
<button>{{foo}}</button>
<script>
export default {
computed: {
foo () {
this.set({ foo: true });
}
}
}
</script>

@ -0,0 +1,8 @@
[{
"message": "Cannot use this.get(...) — it must be passed into the computed function as an argument",
"pos": 73,
"loc": {
"line": 7,
"column": 11
}
}]

@ -0,0 +1,11 @@
{{foo}}
<script>
export default {
computed: {
foo () {
return this.get( 'bar' );
}
}
}
</script>
Loading…
Cancel
Save