You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
135 lines
4.3 KiB
135 lines
4.3 KiB
/** |
|
* @fileoverview disallow 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: { |
|
type: 'suggestion', |
|
docs: { |
|
description: 'disallow usage of `this` in template', |
|
categories: ['vue3-recommended', 'recommended'], |
|
url: 'https://eslint.vuejs.org/rules/this-in-template.html' |
|
}, |
|
fixable: 'code', |
|
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' |
|
/** |
|
* @typedef {object} ScopeStack |
|
* @property {ScopeStack | null} parent |
|
* @property {Identifier[]} nodes |
|
*/ |
|
|
|
/** @type {ScopeStack | null} */ |
|
let scopeStack = null |
|
|
|
return utils.defineTemplateBodyVisitor(context, { |
|
/** @param {VElement} node */ |
|
VElement(node) { |
|
scopeStack = { |
|
parent: scopeStack, |
|
nodes: scopeStack |
|
? scopeStack.nodes.slice() // make copy |
|
: [] |
|
} |
|
if (node.variables) { |
|
for (const variable of node.variables) { |
|
const varNode = variable.id |
|
const name = varNode.name |
|
if (!scopeStack.nodes.some((node) => node.name === name)) { |
|
// Prevent adding duplicates |
|
scopeStack.nodes.push(varNode) |
|
} |
|
} |
|
} |
|
}, |
|
'VElement:exit'() { |
|
scopeStack = scopeStack && scopeStack.parent |
|
}, |
|
...(options === 'never' |
|
? { |
|
/** @param { ThisExpression & { parent: MemberExpression } } node */ |
|
'VExpressionContainer MemberExpression > ThisExpression'(node) { |
|
if (!scopeStack) { |
|
return |
|
} |
|
const propertyName = utils.getStaticPropertyName(node.parent) |
|
if ( |
|
!propertyName || |
|
scopeStack.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, |
|
fix(fixer) { |
|
// node.parent should be some code like `this.test`, `this?.['result']` |
|
return fixer.replaceText(node.parent, propertyName) |
|
}, |
|
message: "Unexpected usage of 'this'." |
|
}) |
|
} |
|
} |
|
: { |
|
/** @param {VExpressionContainer} node */ |
|
VExpressionContainer(node) { |
|
if (!scopeStack) { |
|
return |
|
} |
|
if (node.parent.type === 'VDirectiveKey') { |
|
// We cannot use `.` in dynamic arguments because the right of the `.` becomes a modifier. |
|
// For example, In `:[this.prop]` case, `:[this` is an argument and `prop]` is a modifier. |
|
return |
|
} |
|
if (node.references) { |
|
for (const reference of node.references) { |
|
if ( |
|
!scopeStack.nodes.some( |
|
(el) => el.name === reference.id.name |
|
) |
|
) { |
|
context.report({ |
|
node: reference.id, |
|
loc: reference.id.loc, |
|
message: "Expected 'this'.", |
|
fix(fixer) { |
|
return fixer.insertTextBefore(reference.id, 'this.') |
|
} |
|
}) |
|
} |
|
} |
|
} |
|
} |
|
}) |
|
}) |
|
} |
|
}
|
|
|