no-side-effects-in-computed-properties.js 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. /**
  2. * @fileoverview Don't introduce side effects in computed properties
  3. * @author Michał Sajnóg
  4. */
  5. 'use strict'
  6. const utils = require('../utils')
  7. // ------------------------------------------------------------------------------
  8. // Rule Definition
  9. // ------------------------------------------------------------------------------
  10. module.exports = {
  11. meta: {
  12. type: 'problem',
  13. docs: {
  14. description: 'disallow side effects in computed properties',
  15. category: 'essential',
  16. url: 'https://eslint.vuejs.org/rules/no-side-effects-in-computed-properties.html'
  17. },
  18. fixable: null,
  19. schema: []
  20. },
  21. create (context) {
  22. const forbiddenNodes = []
  23. let scopeStack = { upper: null, body: null }
  24. function onFunctionEnter (node) {
  25. scopeStack = { upper: scopeStack, body: node.body }
  26. }
  27. function onFunctionExit () {
  28. scopeStack = scopeStack.upper
  29. }
  30. return Object.assign({},
  31. {
  32. ':function': onFunctionEnter,
  33. ':function:exit': onFunctionExit,
  34. // this.xxx <=|+=|-=>
  35. 'AssignmentExpression' (node) {
  36. if (node.left.type !== 'MemberExpression') return
  37. if (utils.parseMemberExpression(node.left)[0] === 'this') {
  38. forbiddenNodes.push({
  39. node,
  40. targetBody: scopeStack.body
  41. })
  42. }
  43. },
  44. // this.xxx <++|-->
  45. 'UpdateExpression > MemberExpression' (node) {
  46. if (utils.parseMemberExpression(node)[0] === 'this') {
  47. forbiddenNodes.push({
  48. node,
  49. targetBody: scopeStack.body
  50. })
  51. }
  52. },
  53. // this.xxx.func()
  54. 'CallExpression' (node) {
  55. const code = utils.parseMemberOrCallExpression(node)
  56. const MUTATION_REGEX = /(this.)((?!(concat|slice|map|filter)\().)[^\)]*((push|pop|shift|unshift|reverse|splice|sort|copyWithin|fill)\()/g
  57. if (MUTATION_REGEX.test(code)) {
  58. forbiddenNodes.push({
  59. node,
  60. targetBody: scopeStack.body
  61. })
  62. }
  63. }
  64. },
  65. utils.executeOnVue(context, (obj) => {
  66. const computedProperties = utils.getComputedProperties(obj)
  67. computedProperties.forEach(cp => {
  68. forbiddenNodes.forEach(({ node, targetBody }) => {
  69. if (
  70. cp.value &&
  71. node.loc.start.line >= cp.value.loc.start.line &&
  72. node.loc.end.line <= cp.value.loc.end.line &&
  73. targetBody === cp.value
  74. ) {
  75. context.report({
  76. node: node,
  77. message: 'Unexpected side effect in "{{key}}" computed property.',
  78. data: { key: cp.key }
  79. })
  80. }
  81. })
  82. })
  83. })
  84. )
  85. }
  86. }