diff --git a/src/utils/isThisGetCallExpression.ts b/src/utils/isThisGetCallExpression.ts
new file mode 100644
index 000000000000..81642a389d86
--- /dev/null
+++ b/src/utils/isThisGetCallExpression.ts
@@ -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';
+}
diff --git a/src/utils/walkThroughTopFunctionScope.ts b/src/utils/walkThroughTopFunctionScope.ts
new file mode 100644
index 000000000000..b773bf9a97ba
--- /dev/null
+++ b/src/utils/walkThroughTopFunctionScope.ts
@@ -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;
+ }
+ },
+ });
+}
diff --git a/src/validate/js/propValidators/computed.ts b/src/validate/js/propValidators/computed.ts
index a54d7856b0fb..a1b9afa93769 100644
--- a/src/validate/js/propValidators/computed.ts
+++ b/src/validate/js/propValidators/computed.ts
@@ -2,6 +2,8 @@ import checkForDupes from '../utils/checkForDupes';
import checkForComputedKeys from '../utils/checkForComputedKeys';
import { Validator } from '../../';
import { Node } from '../../../interfaces';
+import walkThroughTopFunctionScope from '../../../utils/walkThroughTopFunctionScope';
+import isThisGetCallExpression from '../../../utils/isThisGetCallExpression';
const isFunctionExpression = new Set([
'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) {
validator.error(
diff --git a/src/validate/js/propValidators/helpers.ts b/src/validate/js/propValidators/helpers.ts
index 4345257e555b..d4960d9e1ebe 100644
--- a/src/validate/js/propValidators/helpers.ts
+++ b/src/validate/js/propValidators/helpers.ts
@@ -3,6 +3,8 @@ import checkForComputedKeys from '../utils/checkForComputedKeys';
import { walk } from 'estree-walker';
import { Validator } from '../../';
import { Node } from '../../../interfaces';
+import walkThroughTopFunctionScope from '../../../utils/walkThroughTopFunctionScope';
+import isThisGetCallExpression from '../../../utils/isThisGetCallExpression';
export default function helpers(validator: Validator, prop: Node) {
if (prop.value.type !== 'ObjectExpression') {
@@ -18,45 +20,24 @@ export default function helpers(validator: Validator, prop: Node) {
prop.value.properties.forEach((prop: Node) => {
if (!/FunctionExpression/.test(prop.value.type)) return;
- let lexicalDepth = 0;
let usesArguments = false;
- walk(prop.value.body, {
- enter(node: Node) {
- if (/^Function/.test(node.type)) {
- lexicalDepth += 1;
- } else if (lexicalDepth === 0) {
- // 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' &&
- node.callee.type === 'MemberExpression' &&
- node.callee.object.type === 'ThisExpression' &&
- node.callee.property.name === 'get' &&
- !node.callee.property.computed
- ) {
- validator.error(
- `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;
- }
- },
+ walkThroughTopFunctionScope(prop.value.body, (node: Node) => {
+ if (isThisGetCallExpression(node) && !node.callee.property.computed) {
+ validator.error(
+ `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;
+ }
});
if (prop.value.params.length === 0 && !usesArguments) {
diff --git a/test/validator/samples/computed-purify-check-no-this/errors.json b/test/validator/samples/computed-purify-check-no-this/errors.json
new file mode 100644
index 000000000000..e1a5e392defa
--- /dev/null
+++ b/test/validator/samples/computed-purify-check-no-this/errors.json
@@ -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
+ }
+}]
diff --git a/test/validator/samples/computed-purify-check-no-this/input.html b/test/validator/samples/computed-purify-check-no-this/input.html
new file mode 100644
index 000000000000..a7e4130e9e5a
--- /dev/null
+++ b/test/validator/samples/computed-purify-check-no-this/input.html
@@ -0,0 +1,11 @@
+
+
+
diff --git a/test/validator/samples/computed-purify-check-this-get/errors.json b/test/validator/samples/computed-purify-check-this-get/errors.json
new file mode 100644
index 000000000000..f5cb473ed282
--- /dev/null
+++ b/test/validator/samples/computed-purify-check-this-get/errors.json
@@ -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
+ }
+}]
diff --git a/test/validator/samples/computed-purify-check-this-get/input.html b/test/validator/samples/computed-purify-check-this-get/input.html
new file mode 100644
index 000000000000..1188c94d9739
--- /dev/null
+++ b/test/validator/samples/computed-purify-check-this-get/input.html
@@ -0,0 +1,11 @@
+{{foo}}
+
+
diff --git a/test/validator/samples/helper-purity-check-this-get/input.html b/test/validator/samples/helper-purity-check-this-get/input.html
index e950152de876..8807be05f6dd 100644
--- a/test/validator/samples/helper-purity-check-this-get/input.html
+++ b/test/validator/samples/helper-purity-check-this-get/input.html
@@ -8,4 +8,4 @@
}
}
}
-
\ No newline at end of file
+