diff --git a/docs/rules/no-unused-properties.md b/docs/rules/no-unused-properties.md index da8fa7d87..4c8300923 100644 --- a/docs/rules/no-unused-properties.md +++ b/docs/rules/no-unused-properties.md @@ -14,7 +14,7 @@ since: v7.0.0 This rule is aimed at eliminating unused properties. ::: warning Note -This rule cannot be checked for use in other components (e.g. `mixins`, Property access via `$refs`) and use in places where the scope cannot be determined. +This rule cannot check for use of properties by other components (e.g. `mixins`, property access via `$refs`) and use in places where the scope cannot be determined. Some access to properties might be implied, for example accessing data or computed via a variable such as `this[varName]`. In this case, the default is to assume all properties, methods, etc. are 'used'. See the `unreferencedOptions` for a more strict interpretation of 'use' in these cases. ::: @@ -56,7 +56,8 @@ This rule cannot be checked for use in other components (e.g. `mixins`, Property "vue/no-unused-properties": ["error", { "groups": ["props"], "deepData": false, - "ignorePublicMembers": false + "ignorePublicMembers": false, + "unreferencedOptions": [] }] } ``` @@ -69,6 +70,7 @@ This rule cannot be checked for use in other components (e.g. `mixins`, Property - `"setup"` - `deepData` (`boolean`) If `true`, the object of the property defined in `data` will be searched deeply. Default is `false`. Include `"data"` in `groups` to use this option. - `ignorePublicMembers` (`boolean`) If `true`, members marked with a [JSDoc `/** @public */` tag](https://jsdoc.app/tags-public.html) will be ignored. Default is `false`. +- `unreferencedOptions` (`string[]`) Array of access methods that should be interpreted as leaving properties unreferenced. Currently, two such methods are available: `unknownMemberAsUnreferenced`, and `returnAsUnreferenced`. See examples below. ### `"groups": ["props", "data"]` @@ -218,6 +220,71 @@ This rule cannot be checked for use in other components (e.g. `mixins`, Property +### `{ "groups": ["computed"], "unreferencedOptions": ["unknownMemberAsUnreferenced"] }` + + + +```vue + + +``` + + + +### `{ "groups": ["computed"], "unreferencedOptions": ["returnAsUnreferenced"] }` + + + +```vue + + +``` + + + ## :rocket: Version This rule was introduced in eslint-plugin-vue v7.0.0 diff --git a/lib/rules/no-unused-properties.js b/lib/rules/no-unused-properties.js index 99a20a40e..1421ad0b8 100644 --- a/lib/rules/no-unused-properties.js +++ b/lib/rules/no-unused-properties.js @@ -55,6 +55,9 @@ const GROUP_SETUP = 'setup' const GROUP_WATCHER = 'watch' const GROUP_EXPOSE = 'expose' +const UNREFERENCED_UNKNOWN_MEMBER = 'unknownMemberAsUnreferenced' +const UNREFERENCED_RETURN = 'returnAsUnreferenced' + const PROPERTY_LABEL = { props: 'property', data: 'data', @@ -206,7 +209,15 @@ module.exports = { uniqueItems: true }, deepData: { type: 'boolean' }, - ignorePublicMembers: { type: 'boolean' } + ignorePublicMembers: { type: 'boolean' }, + unreferencedOptions: { + type: 'array', + items: { + enum: [UNREFERENCED_UNKNOWN_MEMBER, UNREFERENCED_RETURN] + }, + additionalItems: false, + uniqueItems: true + } }, additionalProperties: false } @@ -221,8 +232,17 @@ module.exports = { const groups = new Set(options.groups || [GROUP_PROPERTY]) const deepData = Boolean(options.deepData) const ignorePublicMembers = Boolean(options.ignorePublicMembers) + const unreferencedOptions = new Set(options.unreferencedOptions || []) - const propertyReferenceExtractor = definePropertyReferenceExtractor(context) + const propertyReferenceExtractor = definePropertyReferenceExtractor( + context, + { + unknownMemberAsUnreferenced: unreferencedOptions.has( + UNREFERENCED_UNKNOWN_MEMBER + ), + returnAsUnreferenced: unreferencedOptions.has(UNREFERENCED_RETURN) + } + ) /** @type {TemplatePropertiesContainer} */ const templatePropertiesContainer = { diff --git a/lib/utils/property-references.js b/lib/utils/property-references.js index 7a881af2f..4be7b17c7 100644 --- a/lib/utils/property-references.js +++ b/lib/utils/property-references.js @@ -92,7 +92,10 @@ module.exports = { /** * @param {RuleContext} context The rule context. */ -function definePropertyReferenceExtractor(context) { +function definePropertyReferenceExtractor( + context, + { unknownMemberAsUnreferenced = false, returnAsUnreferenced = false } = {} +) { /** @type {Map} */ const cacheForExpression = new Map() /** @type {Map} */ @@ -314,9 +317,15 @@ function definePropertyReferenceExtractor(context) { if (parent.object === node) { // `arg.foo` const name = utils.getStaticPropertyName(parent) - return name - ? new PropertyReferencesForMember(parent, name, withInTemplate) - : ANY + if (name) { + return new PropertyReferencesForMember( + parent, + name, + withInTemplate + ) + } else { + return unknownMemberAsUnreferenced ? NEVER : ANY + } } return NEVER } @@ -331,12 +340,18 @@ function definePropertyReferenceExtractor(context) { return extractFromExpression(parent, withInTemplate) } case 'ArrowFunctionExpression': - case 'ReturnStatement': case 'VExpressionContainer': case 'Property': case 'ArrayExpression': { return maybeExternalUsed(parent) ? ANY : NEVER } + case 'ReturnStatement': { + if (returnAsUnreferenced) { + return NEVER + } else { + return maybeExternalUsed(parent) ? ANY : NEVER + } + } } return NEVER } diff --git a/tests/lib/rules/no-unused-properties.js b/tests/lib/rules/no-unused-properties.js index a75d83e72..8d4b2b215 100644 --- a/tests/lib/rules/no-unused-properties.js +++ b/tests/lib/rules/no-unused-properties.js @@ -21,6 +21,33 @@ const allOptions = [ ] const deepDataOptions = [{ groups: ['data'], deepData: true }] +const unreferencedOptions = { + // Report errors when accessing via unknown property, e.g. this[varName] + unknownMemberAsUnreferenced: [ + { + groups: ['computed'], + unreferencedOptions: ['unknownMemberAsUnreferenced'] + } + ], + // Report errors when returning this + returnAsUnreferenced: [ + { + groups: ['computed'], + unreferencedOptions: ['returnAsUnreferenced'] + } + ], + // Report all + all: [ + { + groups: ['computed'], + unreferencedOptions: [ + 'unknownMemberAsUnreferenced', + 'returnAsUnreferenced' + ] + } + ] +} + tester.run('no-unused-properties', rule, { valid: [ // a property used in a script expression @@ -1699,7 +1726,6 @@ tester.run('no-unused-properties', rule, { ` } ], - invalid: [ // unused property { @@ -2803,6 +2829,138 @@ tester.run('no-unused-properties', rule, { line: 10 } ] + }, + + // unreferencedOptions: unknownMemberAsUnreferenced + { + filename: 'test.vue', + code: ` + `, + options: unreferencedOptions.unknownMemberAsUnreferenced, + errors: [ + { + message: "'two' of computed property found, but never used.", + line: 8 + } + ] + }, + // unreferencedOptions: returnAsUnreferenced + { + filename: 'test.vue', + code: ` + `, + options: unreferencedOptions.returnAsUnreferenced, + errors: [ + { + message: "'two' of computed property found, but never used.", + line: 8 + } + ] + }, + // unreferencedOptions: returnAsUnreferenced via variable with deepData + { + filename: 'test.vue', + code: ` + + `, + options: [ + { + groups: ['data'], + unreferencedOptions: ['returnAsUnreferenced'], + deepData: true + } + ], + errors: [ + { + message: "'foo.bar' of data found, but never used.", + line: 7 + } + ] + }, + // unreferencedOptions: all + { + filename: 'test.vue', + code: ` + `, + options: unreferencedOptions.all, + errors: [ + { + message: "'two' of computed property found, but never used.", + line: 8 + } + ] } ] })