From 1b3c0853b4798f101a2d05fc255682b6605f9946 Mon Sep 17 00:00:00 2001 From: Armano Date: Wed, 16 Aug 2017 20:36:15 +0200 Subject: [PATCH 1/6] Add options to `no-this-in-template`. fixes #148 --- docs/rules/no-this-in-template.md | 54 ++++++++++++- docs/rules/require-render-return.md | 2 +- lib/rules/no-this-in-template.js | 74 +++++++++++++++--- tests/lib/rules/no-this-in-template.js | 104 +++++++++++++++---------- 4 files changed, 175 insertions(+), 59 deletions(-) diff --git a/docs/rules/no-this-in-template.md b/docs/rules/no-this-in-template.md index 0b877094f..bbc179992 100644 --- a/docs/rules/no-this-in-template.md +++ b/docs/rules/no-this-in-template.md @@ -1,6 +1,4 @@ -# Disallow usage of `this` in template. (no-this-in-template) - -This rule reports expresions that contain `this` keyword in expressions +# enforce usage of `this` in template. (no-this-in-template) ## :book: Rule Details @@ -22,4 +20,52 @@ This rule reports expresions that contain `this` keyword in expressions ## :wrench: Options -Nothing. +Default is set to `never`. + +``` +'vue/no-this-in-template': [2, 'always'|'never'] +``` + +### `"always"` - Always use `this` while accessing properties from vue + +:+1: Examples of **correct** code`: + +```html + +``` + +:-1: Examples of **incorrect** code`: + +```html + +``` + +### `"never"` - Never use expresions that contain `this` keyword in expressions + +:+1: Examples of **correct** code`: + +```html + +``` + +:-1: Examples of **incorrect** code`: + +```html + +``` diff --git a/docs/rules/require-render-return.md b/docs/rules/require-render-return.md index 5a2479f93..44ea2d820 100644 --- a/docs/rules/require-render-return.md +++ b/docs/rules/require-render-return.md @@ -1,6 +1,6 @@ # Enforces render function to always return value (require-render-return) -This rule aims to enforce render function to allways return value +This rule aims to enforce render function to always return value ## :book: Rule Details diff --git a/lib/rules/no-this-in-template.js b/lib/rules/no-this-in-template.js index bec97d76a..64317ec06 100644 --- a/lib/rules/no-this-in-template.js +++ b/lib/rules/no-this-in-template.js @@ -1,5 +1,5 @@ /** - * @fileoverview Disallow usage of `this` in template. + * @fileoverview enforce usage of `this` in template. * @author Armano */ 'use strict' @@ -17,12 +17,16 @@ const utils = require('../utils') module.exports = { meta: { docs: { - description: 'Disallow usage of `this` in template.', + description: 'enforce usage of `this` in template.', category: 'Best Practices', recommended: false }, fixable: null, - schema: [] + schema: [ + { + enum: ['always', 'never'] + } + ] }, /** @@ -32,15 +36,63 @@ module.exports = { * @returns {Object} AST event handlers. */ create (context) { - utils.registerTemplateBodyVisitor(context, { - 'VExpressionContainer ThisExpression' (node) { - context.report({ - node, - loc: node.loc, - message: "Unexpected usage of 'this'." - }) + const options = context.options[0] !== 'always' ? 'never' : 'always' + let scope = { + parent: null, + nodes: [] + } + + function validateNever () { + return { + 'VExpressionContainer ThisExpression' (node) { + if (options === 'never') { + context.report({ + node, + loc: node.loc, + message: "Unexpected usage of 'this'." + }) + } + } } - }) + } + + function validateAlways () { + return { + 'VExpressionContainer' (node) { + if (node.references && options === 'always') { + for (const reference of node.references) { + if (!scope.nodes.some(node => node.name === reference.id.name)) { + context.report({ + node: reference.id, + loc: reference.id.loc, + message: "Expected 'this'." + }) + } + } + } + }, + VElement (node) { + scope = { + parent: scope, + nodes: scope.nodes.slice() // make copy + } + if (node.variables) { + for (const variable of node.variables) { + const varNode = variable.id + const name = varNode.name + if (!scope.nodes.some(node => node.name === name)) { // Prevent adding duplicates + scope.nodes.push(varNode) + } + } + } + }, + 'VElement:exit' (node) { + scope = scope.parent + } + } + } + + utils.registerTemplateBodyVisitor(context, options === 'never' ? validateNever() : validateAlways()) return {} } diff --git a/tests/lib/rules/no-this-in-template.js b/tests/lib/rules/no-this-in-template.js index 991ef74ce..73118905d 100644 --- a/tests/lib/rules/no-this-in-template.js +++ b/tests/lib/rules/no-this-in-template.js @@ -1,5 +1,5 @@ /** - * @fileoverview Disallow usage of `this` in template. + * @fileoverview enforce usage of `this` in template. * @author Armano */ 'use strict' @@ -21,59 +21,77 @@ const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2015 } }) -ruleTester.run('no-this-in-template', rule, { - valid: [ - '', - '', - '', - '', - '', - '', - '', - '' - ], - invalid: [ +function createValidTests (prefix, options) { + return [ { - code: '', - errors: [{ - message: "Unexpected usage of 'this'.", - type: 'ThisExpression' - }] + code: ``, + options }, { - code: '', - errors: [{ - message: "Unexpected usage of 'this'.", - type: 'ThisExpression' - }] + code: ``, + options }, { - code: '', - errors: [{ - message: "Unexpected usage of 'this'.", - type: 'ThisExpression' - }] + code: ``, + options }, { - code: '', - errors: [{ - message: "Unexpected usage of 'this'.", - type: 'ThisExpression' - }] + code: ``, + options }, { - code: '', - errors: [{ - message: "Unexpected usage of 'this'.", - type: 'ThisExpression' - }] + code: ``, + options }, { - code: '', - errors: [{ - message: "Unexpected usage of 'this'.", - type: 'ThisExpression' - }] + code: ``, + options } ] +} + +function createInvalidTests (prefix, options, message, type) { + return [ + { + code: ``, + errors: [{ message, type }], + options + }, + { + code: ``, + errors: [{ message, type }], + options + }, + { + code: ``, + errors: [{ message, type }], + options + }, + { + code: ``, + errors: [{ message, type }], + options + }, + { + code: ``, + errors: [{ message, type }], + options + }, + { + code: ``, + errors: [{ message, type }], + options + } + ] +} + +ruleTester.run('no-this-in-template', rule, { + valid: ['', '', ''] + .concat(createValidTests('', [])) + .concat(createValidTests('', ['never'])) + .concat(createValidTests('this.', ['always'])), + invalid: [] + .concat(createInvalidTests('this.', [], "Unexpected usage of 'this'.", 'ThisExpression')) + .concat(createInvalidTests('this.', ['never'], "Unexpected usage of 'this'.", 'ThisExpression')) + .concat(createInvalidTests('', ['always'], "Expected 'this'.", 'Identifier')) }) From 620259a6c7afa4f469216e5e3c4ed9aabaf5a44f Mon Sep 17 00:00:00 2001 From: Armano Date: Thu, 17 Aug 2017 00:05:57 +0200 Subject: [PATCH 2/6] Add few more advanced tests --- tests/lib/rules/no-this-in-template.js | 45 +++++++++++++++++++------- 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/tests/lib/rules/no-this-in-template.js b/tests/lib/rules/no-this-in-template.js index 73118905d..2cefec3a7 100644 --- a/tests/lib/rules/no-this-in-template.js +++ b/tests/lib/rules/no-this-in-template.js @@ -22,63 +22,84 @@ const ruleTester = new RuleTester({ }) function createValidTests (prefix, options) { + const comment = options.join('') return [ { - code: ``, + code: ``, options }, { - code: ``, + code: ``, options }, { - code: ``, + code: ``, options }, { - code: ``, + code: ``, options }, { - code: ``, + code: ``, options }, { - code: ``, + code: ``, + options + }, + { + code: ``, + options + }, + { + code: ``, + options + }, + { + code: ``, options } ] } function createInvalidTests (prefix, options, message, type) { + const comment = options.join('') return [ { - code: ``, + code: ``, errors: [{ message, type }], options }, { - code: ``, + code: ``, errors: [{ message, type }], options }, { - code: ``, + code: ``, errors: [{ message, type }], options }, { - code: ``, + code: ``, errors: [{ message, type }], options }, { - code: ``, + code: ``, errors: [{ message, type }], options }, { - code: ``, + code: ``, errors: [{ message, type }], options } From d2c6bee2e7d6fe38b122b1551c1d49074d92775a Mon Sep 17 00:00:00 2001 From: Armano Date: Thu, 17 Aug 2017 10:21:34 +0200 Subject: [PATCH 3/6] Rename `no-this-in-template` to `this-in-template` --- docs/rules/{no-this-in-template.md => this-in-template.md} | 4 ++-- lib/rules/{no-this-in-template.js => this-in-template.js} | 2 +- .../lib/rules/{no-this-in-template.js => this-in-template.js} | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) rename docs/rules/{no-this-in-template.md => this-in-template.md} (90%) rename lib/rules/{no-this-in-template.js => this-in-template.js} (97%) rename tests/lib/rules/{no-this-in-template.js => this-in-template.js} (97%) diff --git a/docs/rules/no-this-in-template.md b/docs/rules/this-in-template.md similarity index 90% rename from docs/rules/no-this-in-template.md rename to docs/rules/this-in-template.md index bbc179992..d0646206e 100644 --- a/docs/rules/no-this-in-template.md +++ b/docs/rules/this-in-template.md @@ -1,4 +1,4 @@ -# enforce usage of `this` in template. (no-this-in-template) +# enforce usage of `this` in template. (this-in-template) ## :book: Rule Details @@ -23,7 +23,7 @@ Default is set to `never`. ``` -'vue/no-this-in-template': [2, 'always'|'never'] +'vue/this-in-template': [2, 'always'|'never'] ``` ### `"always"` - Always use `this` while accessing properties from vue diff --git a/lib/rules/no-this-in-template.js b/lib/rules/this-in-template.js similarity index 97% rename from lib/rules/no-this-in-template.js rename to lib/rules/this-in-template.js index 64317ec06..7ac4eb04d 100644 --- a/lib/rules/no-this-in-template.js +++ b/lib/rules/this-in-template.js @@ -30,7 +30,7 @@ module.exports = { }, /** - * Creates AST event handlers for no-this-in-template. + * Creates AST event handlers for this-in-template. * * @param {RuleContext} context - The rule context. * @returns {Object} AST event handlers. diff --git a/tests/lib/rules/no-this-in-template.js b/tests/lib/rules/this-in-template.js similarity index 97% rename from tests/lib/rules/no-this-in-template.js rename to tests/lib/rules/this-in-template.js index 2cefec3a7..c60575972 100644 --- a/tests/lib/rules/no-this-in-template.js +++ b/tests/lib/rules/this-in-template.js @@ -8,7 +8,7 @@ // Requirements // ------------------------------------------------------------------------------ -const rule = require('../../../lib/rules/no-this-in-template') +const rule = require('../../../lib/rules/this-in-template') const RuleTester = require('eslint').RuleTester @@ -106,7 +106,7 @@ function createInvalidTests (prefix, options, message, type) { ] } -ruleTester.run('no-this-in-template', rule, { +ruleTester.run('this-in-template', rule, { valid: ['', '', ''] .concat(createValidTests('', [])) .concat(createValidTests('', ['never'])) From fe2a5da459beb0c6e5ce38c11607863a5025ef95 Mon Sep 17 00:00:00 2001 From: Armano Date: Fri, 18 Aug 2017 12:22:40 +0200 Subject: [PATCH 4/6] Add more edge cases --- lib/rules/this-in-template.js | 63 +++++++++++++++-------------- tests/lib/rules/this-in-template.js | 45 ++++++++++++++++++++- 2 files changed, 76 insertions(+), 32 deletions(-) diff --git a/lib/rules/this-in-template.js b/lib/rules/this-in-template.js index 7ac4eb04d..537ef8b75 100644 --- a/lib/rules/this-in-template.js +++ b/lib/rules/this-in-template.js @@ -42,10 +42,34 @@ module.exports = { nodes: [] } - function validateNever () { - return { - 'VExpressionContainer ThisExpression' (node) { - if (options === 'never') { + utils.registerTemplateBodyVisitor(context, Object.assign({ + VElement (node) { + scope = { + parent: scope, + nodes: scope.nodes.slice() // make copy + } + if (node.variables) { + for (const variable of node.variables) { + const varNode = variable.id + const name = varNode.name + if (!scope.nodes.some(node => node.name === name)) { // Prevent adding duplicates + scope.nodes.push(varNode) + } + } + } + }, + 'VElement:exit' (node) { + scope = scope.parent + } + }, options === 'never' + ? { + 'VExpressionContainer MemberExpression > ThisExpression' (node) { + const propertyName = utils.getStaticPropertyName(node.parent.property) + if (!propertyName) { + return + } + + if (!scope.nodes.some(el => el.name === propertyName)) { context.report({ node, loc: node.loc, @@ -54,14 +78,11 @@ module.exports = { } } } - } - - function validateAlways () { - return { + : { 'VExpressionContainer' (node) { - if (node.references && options === 'always') { + if (node.references) { for (const reference of node.references) { - if (!scope.nodes.some(node => node.name === reference.id.name)) { + if (!scope.nodes.some(el => el.name === reference.id.name)) { context.report({ node: reference.id, loc: reference.id.loc, @@ -70,29 +91,9 @@ module.exports = { } } } - }, - VElement (node) { - scope = { - parent: scope, - nodes: scope.nodes.slice() // make copy - } - if (node.variables) { - for (const variable of node.variables) { - const varNode = variable.id - const name = varNode.name - if (!scope.nodes.some(node => node.name === name)) { // Prevent adding duplicates - scope.nodes.push(varNode) - } - } - } - }, - 'VElement:exit' (node) { - scope = scope.parent } } - } - - utils.registerTemplateBodyVisitor(context, options === 'never' ? validateNever() : validateAlways()) + )) return {} } diff --git a/tests/lib/rules/this-in-template.js b/tests/lib/rules/this-in-template.js index c60575972..df4d6f6af 100644 --- a/tests/lib/rules/this-in-template.js +++ b/tests/lib/rules/this-in-template.js @@ -52,6 +52,30 @@ function createValidTests (prefix, options) { code: ``, options }, + { + code: ``, + options + }, + { + code: ``, + options + }, + { + code: ``, + options + }, + { + code: ``, + options + }, + { + code: ``, + options + }, + { + code: ``, + options + }, { code: ``, options @@ -78,6 +102,16 @@ function createInvalidTests (prefix, options, message, type) { errors: [{ message, type }], options }, + { + code: ``, + errors: [{ message, type }], + options + }, + { + code: ``, + errors: [{ message, type }], + options + }, { code: ``, errors: [{ message, type }], @@ -103,7 +137,16 @@ function createInvalidTests (prefix, options, message, type) { errors: [{ message, type }], options } - ] + ].concat(options[0] === 'always' + ? [] + : [ + { + code: ``, + errors: [{ message, type }], + options + } + ] + ) } ruleTester.run('this-in-template', rule, { From d6eb3fdc87b64422fb07ec6a9a25829f8af7cdfe Mon Sep 17 00:00:00 2001 From: Armano Date: Sat, 19 Aug 2017 14:58:54 +0200 Subject: [PATCH 5/6] Do not remove `this.` when word is reserved --- lib/rules/this-in-template.js | 3 ++- lib/utils/js-reserved.json | 18 ++++++++++++++++++ tests/lib/rules/this-in-template.js | 4 ++++ 3 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 lib/utils/js-reserved.json diff --git a/lib/rules/this-in-template.js b/lib/rules/this-in-template.js index 537ef8b75..2f82008f8 100644 --- a/lib/rules/this-in-template.js +++ b/lib/rules/this-in-template.js @@ -9,6 +9,7 @@ // ------------------------------------------------------------------------------ const utils = require('../utils') +const RESERVED_NAMES = new Set(require('../utils/js-reserved.json')) // ------------------------------------------------------------------------------ // Rule Definition @@ -69,7 +70,7 @@ module.exports = { return } - if (!scope.nodes.some(el => el.name === propertyName)) { + if (!scope.nodes.some(el => el.name === propertyName) && !RESERVED_NAMES.has(propertyName)) { context.report({ node, loc: node.loc, diff --git a/lib/utils/js-reserved.json b/lib/utils/js-reserved.json new file mode 100644 index 000000000..9b3eb11cc --- /dev/null +++ b/lib/utils/js-reserved.json @@ -0,0 +1,18 @@ +[ + "abstract", "arguments", "await", "boolean", + "break", "byte", "case", "catch", + "char", "class", "const", "continue", + "debugger", "default", "delete", "do", + "double", "else", "enum", "eval", + "export", "extends", "false", "final", + "finally", "float", "for", "function", + "goto", "if", "implements", "import", + "in", "instanceof", "int", "interface", + "let", "long", "native", "new", + "null", "package", "private", "protected", + "public", "return", "short", "static", + "super", "switch", "synchronized", "this", + "throw", "throws", "transient", "true", + "try", "typeof", "var", "void", + "volatile", "while", "with", "yield" +] diff --git a/tests/lib/rules/this-in-template.js b/tests/lib/rules/this-in-template.js index df4d6f6af..bf357d4fc 100644 --- a/tests/lib/rules/this-in-template.js +++ b/tests/lib/rules/this-in-template.js @@ -76,6 +76,10 @@ function createValidTests (prefix, options) { code: ``, options }, + { + code: ``, + options + }, { code: ``, options From f96baddb2342134d1e8e0640f5c7c3fdcae9910e Mon Sep 17 00:00:00 2001 From: Armano Date: Sat, 19 Aug 2017 18:54:55 +0200 Subject: [PATCH 6/6] Add checks for *array like* accesed properties withc can't be converted to properties --- lib/rules/this-in-template.js | 18 ++++++++++-------- package.json | 2 +- tests/lib/rules/this-in-template.js | 17 +++++++++++++++++ 3 files changed, 28 insertions(+), 9 deletions(-) diff --git a/lib/rules/this-in-template.js b/lib/rules/this-in-template.js index 2f82008f8..3581aaaf5 100644 --- a/lib/rules/this-in-template.js +++ b/lib/rules/this-in-template.js @@ -66,17 +66,19 @@ module.exports = { ? { 'VExpressionContainer MemberExpression > ThisExpression' (node) { const propertyName = utils.getStaticPropertyName(node.parent.property) - if (!propertyName) { + if (!propertyName || + scope.nodes.some(el => el.name === propertyName) || + RESERVED_NAMES.has(propertyName) || // this.class | this['class'] + /^[0-9].*$|[^a-zA-Z0-9_]/.test(propertyName) // this['0aaaa'] | this['foo-bar bas'] + ) { return } - if (!scope.nodes.some(el => el.name === propertyName) && !RESERVED_NAMES.has(propertyName)) { - context.report({ - node, - loc: node.loc, - message: "Unexpected usage of 'this'." - }) - } + context.report({ + node, + loc: node.loc, + message: "Unexpected usage of 'this'." + }) } } : { diff --git a/package.json b/package.json index b12c22b53..1eb5e20a2 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ }, "dependencies": { "requireindex": "^1.1.0", - "vue-eslint-parser": "2.0.0-beta.7" + "vue-eslint-parser": "2.0.0-beta.10" }, "devDependencies": { "@types/node": "^4.2.16", diff --git a/tests/lib/rules/this-in-template.js b/tests/lib/rules/this-in-template.js index bf357d4fc..a35c08f8e 100644 --- a/tests/lib/rules/this-in-template.js +++ b/tests/lib/rules/this-in-template.js @@ -80,6 +80,18 @@ function createValidTests (prefix, options) { code: ``, options }, + { + code: ``, + options + }, + { + code: ``, + options + }, + { + code: ``, + options + }, { code: ``, options @@ -148,6 +160,11 @@ function createInvalidTests (prefix, options, message, type) { code: ``, errors: [{ message, type }], options + }, + { + code: ``, + errors: [{ message, type }], + options } ] )