Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to show warnings when props could be accessed via this #2101

Merged
merged 14 commits into from
Apr 13, 2023

Conversation

dsl101
Copy link
Contributor

@dsl101 dsl101 commented Mar 8, 2023

Ref #1636, this is my first stab at adding an option to not treat generic access to this as 'using' properties / data / computed, etc. If this is the right approach, I can try adding tests if needed. It's more general I think than what you'd proposed in that issue, but for my use case it works well.

Ref vuejs#1636, this is my first stab at adding an option to not treat generic access to `this` as 'using' properties / data / computed, etc. If this is the right approach, I can try adding tests if needed. It's more general I think than what you'd proposed in that issue, but for my use case it works well.
@ota-meshi
Copy link
Member

ota-meshi commented Mar 9, 2023

Thank you for this PR.
But, I'm not sure what the options you added would change. Could you please add test cases and documentation?

@ota-meshi ota-meshi marked this pull request as draft March 9, 2023 07:46
@dsl101
Copy link
Contributor Author

dsl101 commented Mar 9, 2023

OK, before I go too far, can I check the original issue makes sense to you (it was a while ago, but I just got some time to revisit)? What I currently see (and am still to be convinced this is not the wrong behaviour) is that the plugin considers all elements in this code as 'used' and shows no warnings:

<script>
  export default {
    computed: {
      life () {
        return 42
      }
    },
    methods: {
      getThis () {
        return this
      },
    }
  }
</script>

My understanding is that is because returning this from a method means 'something' could access any prop, data, computed, method, etc. I think this is a big assumption, and as you mentioned, you can access most things via this.$refs anyway, so why warn about anything? I would prefer this to apply to plain this too, unless the item is marked with the JSDoc @public. However, your argument was not to change the current default behaviour but introduce an option.

I tried to follow your example in #1636 whereby the option stopReporting would default to ThisExpression, and effectively stop the plugin from reporting warnings when it encountered an ambiguous access via this. For my configuration, I will set stopReporting to an empty array, and get the warnings back.

If that all makes sense, I will write this up in docs, and add a test case—but only if you still think this is the right way to go...

@ota-meshi
Copy link
Member

Thanks for the explanation. But I'm still not sure. I need the specific source code. I don't think the current changes match what you want to do.

@dsl101
Copy link
Contributor Author

dsl101 commented Mar 9, 2023

Thanks for the explanation. But I'm still not sure. I need the specific source code. I don't think the current changes match what you want to do.

I'm not sure what you mean by this? I've set this up locally with my changes, and by putting stopReporting: [] in my eslint config, all my unused props are warned about again. This is my .eslintrc.js:

module.exports = {
  extends: [
    // add more generic rulesets here, such as:
    // 'eslint:recommended',
    // 'plugin:vue/vue3-recommended',
    'plugin:vue/recommended' // Use this if you are using Vue.js 2.x.
  ],
  rules: {
    // override/add rules settings here, such as:
    // 'vue/no-unused-vars': 'error'
    'vue/no-unused-properties': ['warn', {
      'groups': [ 'props', 'data', 'computed', 'methods' ],
      'deepData': true,
      'ignorePublicMembers': true,
      'stopReporting': []
    }],
  }
}

and in Sublimetext, I now see this:
image

@dsl101
Copy link
Contributor Author

dsl101 commented Mar 9, 2023

OK, I think I see what you mean. The change is too general at the moment, and also still warns about explicit accesses such as this.life. I will keep looking at where the general this or this[variable] access is detected.

@dsl101
Copy link
Contributor Author

dsl101 commented Mar 9, 2023

Right, I think part of the solution is in utils/property-references.js in function extractFromExpression(). This section at (currently) line 313. I've marked up where I think the case of this.life vs const h = 'life'; this[h] is handled:

        case 'MemberExpression': {
          if (parent.object === node) {
            // `arg.foo`
            const name = utils.getStaticPropertyName(parent)

            return name
              ? new PropertyReferencesForMember(parent, name, withInTemplate)
              : ANY  // ←----- This line should return NEVER to avoid suppressing warnings?
          }
          return NEVER
        }

With const h = 'life'; this[h], utils.getStaticPropertyName() returns null (as it is a computed node), and so extractFromExpression() returns ANY, and suppresses all warnings.

However, I can't see how to access the configuration options within that utility function, to selectively return NEVER.

@dsl101
Copy link
Contributor Author

dsl101 commented Mar 9, 2023

I added the logic into what I think is the right place. Please see if this is closer to what you had in mind with the options.

@ota-meshi
Copy link
Member

I'm still not quite sure what this PR change is trying to do. Could you add test cases and documentation to clarify what you want to do?

@dsl101
Copy link
Contributor Author

dsl101 commented Mar 10, 2023

I will try, but I'm not sure just documenting the new option is enough—especially since it's just an implementation of your own suggestion... Perhaps this example is clearer? Take this code—which of the computed items would you expect to be warned about as 'unused'?

<script>
  export default {
    computed: {
      one () {
        return 1
      },
      two () {
        return 2
      },
      three () {
        return 3
      },
    },
    methods: {
      handler () {
        console.log(this.one)
        const i = 'two'  // Could be two or something else
        console.log(this[i])
        return this
      },
    }
  }
</script>

The current behaviour of the plugin is to warn about none of them being unused, despite only one being explicitly used in the handler(). I would like an option to be able to warn about two and three being unused in this case. Now, the ideal solution would only show three as unused, since two is actually logged to the console, but I understand this is extremely hard if not impossible to determine with static analysis. So the 2 ways in which two and three might be accessed, from your code in #1636, are via this[varName] and via return this. So I've added options, as you suggested, to stop reporting when those access methods are used, and set them as the default. Then I can remove those options via stopReporting: [] in my config, and the warnings show up again.

If that makes it clear what I'm trying to achieve, I'm happy to add test cases and try to document that. But if it's still not clear, I'm not sure how else to describe the issue...

@dsl101
Copy link
Contributor Author

dsl101 commented Mar 17, 2023

Does this change make sense now? I will try to update the documentation to explain if so, but want to make sure you're ok with it.

@ota-meshi
Copy link
Member

I am very busy right now with my daughters having the flu. So I can't check this change yet.

@dsl101
Copy link
Contributor Author

dsl101 commented Mar 17, 2023

Sorry to hear that—no problem at all, just checking in.

@ota-meshi
Copy link
Member

Thank you for adding the test cases.

But I think the PR change still doesn't handle the new options well.
For example, I don't think the following case can be handled well.

    {
      filename: 'test.vue',
      code: `
      <script>
        export default {
          data () {
            return {
              foo: {
                bar: 1
              },
              baz: 2
            }
          },
          methods: {
            handler () {
              console.log(this.baz)
              return this.foo
            },
          }
        }
      </script>
      `,
      options: [{
        groups: ['data'],
        deepData: true,
        stopReporting: []
      }],
      errors: [
        {
          message: "'foo.bar' of data found, but never used.",
          line: 7,
        }
      ]
    }

To handle them better, I think we should pass an option to lib/utils/property-references.js's definePropertyReferenceExtractor to change it to return NEVER when a ReturnStatement is encountered. I think the MemberExpression should also be handled within the definePropertyReferenceExtractor.

@dsl101
Copy link
Contributor Author

dsl101 commented Mar 20, 2023

Yes, that's what I concluded in this comment, but I wasn't sure if passing options down there was even possible let alone desirable. When I get a bit more time, I'll try to update the PR.

@dsl101
Copy link
Contributor Author

dsl101 commented Mar 20, 2023

OK, so one clarification; the deepData option is handled in the rule, so why not the stopReporting option?

In fact, looking closer, I'm not sure exactly how to pass the options down, without the ENUM to test against, or by converting to booleans for each option (STOPREPORTING_UNKNOWN & STOPREPORTING_RETURN for now). And those need to go into extractFromExpression(), which is called from a lot of places, and so would need additional arguments. It seems like it's creating a lot of dependency within that function...

@dsl101
Copy link
Contributor Author

dsl101 commented Mar 20, 2023

OK, I've had a go at passing the options in. I'm not sure it's the most elegant way, and the defaults (true for both) are now baked in to the utility function, which I don't think is ideal. But it has no concept of the options structure that I can see.

Also, that utility function is called from no-undef-properties.js, but I don't think that rule is affected by these new options.

@dsl101
Copy link
Contributor Author

dsl101 commented Mar 24, 2023

My latest changes do work as expected here I think now—I've added an additional test. The new options do not mention the word 'this' any more. If you're happy with the changes as they are, I'll write up in the documentation.

@dsl101 dsl101 marked this pull request as ready for review April 7, 2023 12:22
Copy link
Member

@ota-meshi ota-meshi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

Copy link
Member

@FloEdelmann FloEdelmann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me, especially the documentation and relatively simple implementation 👍

I have just one question, otherwise this is good to go :)

lib/rules/no-unused-properties.js Outdated Show resolved Hide resolved
Copy link
Member

@FloEdelmann FloEdelmann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tests on CircleCI are failing at an unrelated file, maybe they are just flaky @ota-meshi.

Other than that, this looks good to me 🙂

@ota-meshi ota-meshi merged commit 31a38a9 into vuejs:master Apr 13, 2023
7 checks passed
@dsl101 dsl101 deleted the patch-1 branch April 14, 2023 13:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants