diff --git a/docs/rules/no-this-in-template.md b/docs/rules/no-this-in-template.md deleted file mode 100644 index 0b877094f..000000000 --- a/docs/rules/no-this-in-template.md +++ /dev/null @@ -1,25 +0,0 @@ -# Disallow usage of `this` in template. (no-this-in-template) - -This rule reports expresions that contain `this` keyword in expressions - -## :book: Rule Details - -:-1: Examples of **incorrect** code for this rule: - -```html - -``` - -:+1: Examples of **correct** code for this rule: - -```html - -``` - -## :wrench: Options - -Nothing. 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/docs/rules/this-in-template.md b/docs/rules/this-in-template.md new file mode 100644 index 000000000..d0646206e --- /dev/null +++ b/docs/rules/this-in-template.md @@ -0,0 +1,71 @@ +# enforce usage of `this` in template. (this-in-template) + +## :book: Rule Details + +:-1: Examples of **incorrect** code for this rule: + +```html + +``` + +:+1: Examples of **correct** code for this rule: + +```html + +``` + +## :wrench: Options + +Default is set to `never`. + +``` +'vue/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/lib/rules/no-this-in-template.js b/lib/rules/no-this-in-template.js deleted file mode 100644 index bec97d76a..000000000 --- a/lib/rules/no-this-in-template.js +++ /dev/null @@ -1,47 +0,0 @@ -/** - * @fileoverview Disallow usage of `this` in template. - * @author Armano - */ -'use strict' - -// ------------------------------------------------------------------------------ -// Requirements -// ------------------------------------------------------------------------------ - -const utils = require('../utils') - -// ------------------------------------------------------------------------------ -// Rule Definition -// ------------------------------------------------------------------------------ - -module.exports = { - meta: { - docs: { - description: 'Disallow usage of `this` in template.', - category: 'Best Practices', - recommended: false - }, - fixable: null, - schema: [] - }, - - /** - * Creates AST event handlers for no-this-in-template. - * - * @param {RuleContext} context - The rule context. - * @returns {Object} AST event handlers. - */ - create (context) { - utils.registerTemplateBodyVisitor(context, { - 'VExpressionContainer ThisExpression' (node) { - context.report({ - node, - loc: node.loc, - message: "Unexpected usage of 'this'." - }) - } - }) - - return {} - } -} diff --git a/lib/rules/this-in-template.js b/lib/rules/this-in-template.js new file mode 100644 index 000000000..3581aaaf5 --- /dev/null +++ b/lib/rules/this-in-template.js @@ -0,0 +1,103 @@ +/** + * @fileoverview enforce usage of `this` in template. + * @author Armano + */ +'use strict' + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +const utils = require('../utils') +const RESERVED_NAMES = new Set(require('../utils/js-reserved.json')) + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +module.exports = { + meta: { + docs: { + description: 'enforce usage of `this` in template.', + category: 'Best Practices', + recommended: false + }, + fixable: null, + schema: [ + { + enum: ['always', 'never'] + } + ] + }, + + /** + * Creates AST event handlers for this-in-template. + * + * @param {RuleContext} context - The rule context. + * @returns {Object} AST event handlers. + */ + create (context) { + const options = context.options[0] !== 'always' ? 'never' : 'always' + let scope = { + parent: null, + nodes: [] + } + + 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 || + 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 + } + + context.report({ + node, + loc: node.loc, + message: "Unexpected usage of 'this'." + }) + } + } + : { + 'VExpressionContainer' (node) { + if (node.references) { + for (const reference of node.references) { + if (!scope.nodes.some(el => el.name === reference.id.name)) { + context.report({ + node: reference.id, + loc: reference.id.loc, + message: "Expected 'this'." + }) + } + } + } + } + } + )) + + return {} + } +} 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/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/no-this-in-template.js b/tests/lib/rules/no-this-in-template.js deleted file mode 100644 index 991ef74ce..000000000 --- a/tests/lib/rules/no-this-in-template.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * @fileoverview Disallow usage of `this` in template. - * @author Armano - */ -'use strict' - -// ------------------------------------------------------------------------------ -// Requirements -// ------------------------------------------------------------------------------ - -const rule = require('../../../lib/rules/no-this-in-template') - -const RuleTester = require('eslint').RuleTester - -// ------------------------------------------------------------------------------ -// Tests -// ------------------------------------------------------------------------------ - -const ruleTester = new RuleTester({ - parser: 'vue-eslint-parser', - parserOptions: { ecmaVersion: 2015 } -}) - -ruleTester.run('no-this-in-template', rule, { - valid: [ - '', - '', - '', - '', - '', - '', - '', - '' - ], - invalid: [ - { - code: '', - errors: [{ - message: "Unexpected usage of 'this'.", - type: 'ThisExpression' - }] - }, - { - code: '', - errors: [{ - message: "Unexpected usage of 'this'.", - type: 'ThisExpression' - }] - }, - { - code: '', - errors: [{ - message: "Unexpected usage of 'this'.", - type: 'ThisExpression' - }] - }, - { - code: '', - errors: [{ - message: "Unexpected usage of 'this'.", - type: 'ThisExpression' - }] - }, - { - code: '', - errors: [{ - message: "Unexpected usage of 'this'.", - type: 'ThisExpression' - }] - }, - { - code: '', - errors: [{ - message: "Unexpected usage of 'this'.", - type: 'ThisExpression' - }] - } - ] -}) diff --git a/tests/lib/rules/this-in-template.js b/tests/lib/rules/this-in-template.js new file mode 100644 index 000000000..a35c08f8e --- /dev/null +++ b/tests/lib/rules/this-in-template.js @@ -0,0 +1,182 @@ +/** + * @fileoverview enforce usage of `this` in template. + * @author Armano + */ +'use strict' + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +const rule = require('../../../lib/rules/this-in-template') + +const RuleTester = require('eslint').RuleTester + +// ------------------------------------------------------------------------------ +// Tests +// ------------------------------------------------------------------------------ + +const ruleTester = new RuleTester({ + parser: 'vue-eslint-parser', + parserOptions: { ecmaVersion: 2015 } +}) + +function createValidTests (prefix, options) { + const comment = options.join('') + return [ + { + code: ``, + options + }, + { + code: ``, + options + }, + { + code: ``, + options + }, + { + code: ``, + options + }, + { + code: ``, + options + }, + { + code: ``, + options + }, + { + code: ``, + options + }, + { + code: ``, + options + }, + { + code: ``, + options + }, + { + code: ``, + options + }, + { + code: ``, + options + }, + { + code: ``, + options + }, + { + code: ``, + options + }, + { + code: ``, + options + }, + { + code: ``, + options + }, + { + code: ``, + options + }, + { + code: ``, + options + }, + { + code: ``, + options + }, + { + code: ``, + options + } + ] +} + +function createInvalidTests (prefix, options, message, type) { + const comment = options.join('') + 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 + }, + { + code: ``, + errors: [{ message, type }], + options + }, + { + code: ``, + errors: [{ message, type }], + options + } + ].concat(options[0] === 'always' + ? [] + : [ + { + code: ``, + errors: [{ message, type }], + options + }, + { + code: ``, + errors: [{ message, type }], + options + } + ] + ) +} + +ruleTester.run('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')) +})