| /** |
| * @fileoverview Enforces calling `super` in lifecycle methods |
| * @author James Garbutt <https://github.com/43081j> |
| */ |
| import { isLitClass } from '../util.js'; |
| const methodNames = ['connectedCallback', 'disconnectedCallback', 'update']; |
| //------------------------------------------------------------------------------ |
| // Rule Definition |
| //------------------------------------------------------------------------------ |
| export const rule = { |
| meta: { |
| docs: { |
| description: 'Enforces calling `super` in lifecycle methods', |
| recommended: false, |
| url: 'https://github.com/43081j/eslint-plugin-lit/blob/master/docs/rules/lifecycle-super.md' |
| }, |
| schema: [], |
| messages: { |
| callSuper: 'You must call `super.{{method}}` to avoid interrupting ' + |
| 'the lit rendering lifecycle' |
| } |
| }, |
| create(context) { |
| // variables should be defined here |
| let inElement = false; |
| let currentMethod = null; |
| let superSeen = false; |
| //---------------------------------------------------------------------- |
| // Helpers |
| //---------------------------------------------------------------------- |
| /** |
| * Class entered |
| * |
| * @param {ESTree.Class} node Node entered |
| * @return {void} |
| */ |
| function classEnter(node) { |
| if (!isLitClass(node, context)) { |
| return; |
| } |
| inElement = true; |
| } |
| /** |
| * Class exited |
| * |
| * @return {void} |
| */ |
| function classExit() { |
| inElement = false; |
| } |
| /** |
| * Method entered |
| * |
| * @param {ESTree.MethodDefinition} node Node entered |
| * @return {void} |
| */ |
| function methodEnter(node) { |
| if (!inElement || |
| node.static === true || |
| node.kind !== 'method' || |
| node.key.type !== 'Identifier' || |
| !methodNames.includes(node.key.name)) { |
| return; |
| } |
| currentMethod = node.key.name; |
| } |
| /** |
| * Method exited |
| * |
| * @param {ESTree.MethodDefinition} node Node entered |
| * @return {void} |
| */ |
| function methodExit(node) { |
| if (currentMethod !== null && !superSeen) { |
| context.report({ |
| node, |
| messageId: 'callSuper', |
| data: { |
| method: currentMethod |
| } |
| }); |
| } |
| currentMethod = null; |
| superSeen = false; |
| } |
| /** |
| * Call expression entered |
| * @param {ESTree.CallExpression} node Node entered |
| * @return {void} |
| */ |
| function callExpressionEnter(node) { |
| if (currentMethod === null) { |
| return; |
| } |
| if (node.callee.type === 'MemberExpression' && |
| node.callee.object.type === 'Super' && |
| node.callee.property.type === 'Identifier' && |
| node.callee.property.name === currentMethod) { |
| superSeen = true; |
| } |
| } |
| //---------------------------------------------------------------------- |
| // Public |
| //---------------------------------------------------------------------- |
| return { |
| ClassExpression: (node) => classEnter(node), |
| ClassDeclaration: (node) => classEnter(node), |
| 'ClassExpression:exit': classExit, |
| 'ClassDeclaration:exit': classExit, |
| MethodDefinition: (node) => methodEnter(node), |
| 'MethodDefinition:exit': methodExit, |
| CallExpression: (node) => callExpressionEnter(node) |
| }; |
| } |
| }; |