|  | 
|  | 1 | +/** | 
|  | 2 | + * @fileoverview enforce valid `.sync` modifier on `v-bind` directives | 
|  | 3 | + * @author Yosuke Ota | 
|  | 4 | + */ | 
|  | 5 | +'use strict' | 
|  | 6 | + | 
|  | 7 | +// ------------------------------------------------------------------------------ | 
|  | 8 | +// Requirements | 
|  | 9 | +// ------------------------------------------------------------------------------ | 
|  | 10 | + | 
|  | 11 | +const utils = require('../utils') | 
|  | 12 | + | 
|  | 13 | +// ------------------------------------------------------------------------------ | 
|  | 14 | +// Helpers | 
|  | 15 | +// ------------------------------------------------------------------------------ | 
|  | 16 | + | 
|  | 17 | +/** | 
|  | 18 | + * Check whether the given node is valid or not. | 
|  | 19 | + * @param {ASTNode} node The element node to check. | 
|  | 20 | + * @returns {boolean} `true` if the node is valid. | 
|  | 21 | + */ | 
|  | 22 | +function isValidElement (node) { | 
|  | 23 | +  if ( | 
|  | 24 | +    (!utils.isHtmlElementNode(node) && !utils.isSvgElementNode(node)) || | 
|  | 25 | +    utils.isHtmlWellKnownElementName(node.rawName) || | 
|  | 26 | +    utils.isSvgWellKnownElementName(node.rawName) | 
|  | 27 | +  ) { | 
|  | 28 | +    // non Vue-component | 
|  | 29 | +    return false | 
|  | 30 | +  } | 
|  | 31 | +  return true | 
|  | 32 | +} | 
|  | 33 | + | 
|  | 34 | +/** | 
|  | 35 | + * Check whether the given node can be LHS. | 
|  | 36 | + * @param {ASTNode} node The node to check. | 
|  | 37 | + * @returns {boolean} `true` if the node can be LHS. | 
|  | 38 | + */ | 
|  | 39 | +function isLhs (node) { | 
|  | 40 | +  return Boolean(node) && ( | 
|  | 41 | +    node.type === 'Identifier' || | 
|  | 42 | +    node.type === 'MemberExpression' | 
|  | 43 | +  ) | 
|  | 44 | +} | 
|  | 45 | + | 
|  | 46 | +// ------------------------------------------------------------------------------ | 
|  | 47 | +// Rule Definition | 
|  | 48 | +// ------------------------------------------------------------------------------ | 
|  | 49 | + | 
|  | 50 | +module.exports = { | 
|  | 51 | +  meta: { | 
|  | 52 | +    type: 'problem', | 
|  | 53 | +    docs: { | 
|  | 54 | +      description: 'enforce valid `.sync` modifier on `v-bind` directives', | 
|  | 55 | +      category: undefined, | 
|  | 56 | +      url: 'https://eslint.vuejs.org/rules/valid-v-bind-sync.html' | 
|  | 57 | +    }, | 
|  | 58 | +    fixable: null, | 
|  | 59 | +    schema: [], | 
|  | 60 | +    messages: { | 
|  | 61 | +      unexpectedInvalidElement: "'.sync' modifiers aren't supported on <{{name}}> non Vue-components.", | 
|  | 62 | +      unexpectedNonLhsExpression: "'.sync' modifiers require the attribute value which is valid as LHS.", | 
|  | 63 | +      unexpectedUpdateIterationVariable: "'.sync' modifiers cannot update the iteration variable '{{varName}}' itself." | 
|  | 64 | +    } | 
|  | 65 | +  }, | 
|  | 66 | + | 
|  | 67 | +  create (context) { | 
|  | 68 | +    return utils.defineTemplateBodyVisitor(context, { | 
|  | 69 | +      "VAttribute[directive=true][key.name.name='bind']" (node) { | 
|  | 70 | +        if (!node.key.modifiers.map(mod => mod.name).includes('sync')) { | 
|  | 71 | +          return | 
|  | 72 | +        } | 
|  | 73 | +        const element = node.parent.parent | 
|  | 74 | +        const name = element.name | 
|  | 75 | + | 
|  | 76 | +        if (!isValidElement(element)) { | 
|  | 77 | +          context.report({ | 
|  | 78 | +            node, | 
|  | 79 | +            loc: node.loc, | 
|  | 80 | +            messageId: 'unexpectedInvalidElement', | 
|  | 81 | +            data: { name } | 
|  | 82 | +          }) | 
|  | 83 | +        } | 
|  | 84 | + | 
|  | 85 | +        if (node.value) { | 
|  | 86 | +          if (!isLhs(node.value.expression)) { | 
|  | 87 | +            context.report({ | 
|  | 88 | +              node, | 
|  | 89 | +              loc: node.loc, | 
|  | 90 | +              messageId: 'unexpectedNonLhsExpression' | 
|  | 91 | +            }) | 
|  | 92 | +          } | 
|  | 93 | + | 
|  | 94 | +          for (const reference of node.value.references) { | 
|  | 95 | +            const id = reference.id | 
|  | 96 | +            if (id.parent.type !== 'VExpressionContainer') { | 
|  | 97 | +              continue | 
|  | 98 | +            } | 
|  | 99 | +            const variable = reference.variable | 
|  | 100 | +            if (variable) { | 
|  | 101 | +              context.report({ | 
|  | 102 | +                node, | 
|  | 103 | +                loc: node.loc, | 
|  | 104 | +                messageId: 'unexpectedUpdateIterationVariable', | 
|  | 105 | +                data: { varName: id.name } | 
|  | 106 | +              }) | 
|  | 107 | +            } | 
|  | 108 | +          } | 
|  | 109 | +        } | 
|  | 110 | +      } | 
|  | 111 | +    }) | 
|  | 112 | +  } | 
|  | 113 | +} | 
0 commit comments