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 vue/require-typed-object-prop rule #1983

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
c4b20ca
add rule template
przemyslawjanpietrzak Sep 5, 2022
49d0f30
prepare documentation
przemyslawjanpietrzak Sep 5, 2022
9a5320b
prepare force types on object implementation
przemyslawjanpietrzak Sep 19, 2022
681608f
Merge branch 'master' into feat/force-types-on-object-props
przemyslawjanpietrzak Oct 3, 2022
bfa7a9f
fix some unit tests
przemyslawjanpietrzak Oct 3, 2022
2d88680
Merge branch 'master' into feat/force-types-on-object-props
przemyslawjanpietrzak Feb 20, 2023
92c09a8
remove double blank lines
przemyslawjanpietrzak Feb 20, 2023
95aa2a3
set proper category
przemyslawjanpietrzak Feb 20, 2023
96eb868
update docs
przemyslawjanpietrzak Feb 20, 2023
9264f63
Apply suggestions from code review
przemyslawjanpietrzak Feb 23, 2023
1183f8f
add new test case
przemyslawjanpietrzak Feb 23, 2023
75fc825
add new test case & update docs
przemyslawjanpietrzak Feb 23, 2023
9161964
rewrite force-types-on-object-props
przemyslawjanpietrzak Feb 28, 2023
1aa1de6
update docs
przemyslawjanpietrzak Feb 28, 2023
534a2f6
rewrite rule
przemyslawjanpietrzak Mar 22, 2023
9e375c0
rename rule
przemyslawjanpietrzak Jun 5, 2023
cf4f722
Merge branch 'master' into feat/force-types-on-object-props
przemyslawjanpietrzak Jun 5, 2023
ae47555
Merge branch 'master' into feat/force-types-on-object-props
FloEdelmann Jun 21, 2023
fa69006
Fix TypeScript type inference
FloEdelmann Jun 21, 2023
6398ced
Also report untyped array props
FloEdelmann Jun 21, 2023
30145fd
Fix docs
FloEdelmann Jun 21, 2023
d6d87f2
Regenerate docs
FloEdelmann Jun 21, 2023
e5794b0
Rename to `vue/require-typed-object-prop`
FloEdelmann Jun 21, 2023
a0bc1c1
Fix docs
FloEdelmann Jun 21, 2023
bbe3a61
Better docs
FloEdelmann Jun 21, 2023
31009cf
Simplify logic and treat `as any` and `as unknown` as valid
FloEdelmann Jun 21, 2023
934030b
Rename variables
FloEdelmann Jun 21, 2023
7143dc5
Add suggestions to rule, use message IDs
FloEdelmann Jun 21, 2023
dbd17e3
Simplify docs
FloEdelmann Jun 21, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/rules/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@ For example:
| [vue/require-macro-variable-name](./require-macro-variable-name.md) | require a certain macro variable name | :bulb: | :hammer: |
| [vue/require-name-property](./require-name-property.md) | require a name property in Vue components | :bulb: | :hammer: |
| [vue/require-prop-comment](./require-prop-comment.md) | require props to have a comment | | :hammer: |
| [vue/require-typed-object-prop](./require-typed-object-prop.md) | enforce adding type declarations to object props | :bulb: | :hammer: |
| [vue/require-typed-ref](./require-typed-ref.md) | require `ref` and `shallowRef` functions to be strongly typed | | :hammer: |
| [vue/script-indent](./script-indent.md) | enforce consistent indentation in `<script>` | :wrench: | :lipstick: |
| [vue/sort-keys](./sort-keys.md) | enforce sort-keys in a manner that is compatible with order-in-components | | :hammer: |
Expand Down
50 changes: 50 additions & 0 deletions docs/rules/require-typed-object-prop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---
pageClass: rule-details
sidebarDepth: 0
title: vue/require-typed-object-prop
description: enforce adding type declarations to object props
---
# vue/require-typed-object-prop

> enforce adding type declarations to object props

- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> ***This rule has not been released yet.*** </badge>
- :bulb: Some problems reported by this rule are manually fixable by editor [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).

## :book: Rule Details

Prevent missing type declarations for non-primitive object props in TypeScript projects.

<eslint-code-block :rules="{'vue/require-typed-object-prop': ['error']}">

```vue
<script lang="ts">
export default {
props: {
// ✗ BAD
bad1: Object,
bad2: { type: Array },

// ✓ GOOD
good1: Object as PropType<Anything>,
good2: { type: Array as PropType<Anything[]> },
good3: [String, Boolean], // or any other primitive type
}
}
</script>
```

</eslint-code-block>

## :wrench: Options

Nothing.

## :mute: When Not To Use It

When you're not using TypeScript in the project.

## :mag: Implementation

- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/require-typed-object-prop.js)
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/require-typed-object-prop.js)
1 change: 1 addition & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@
'require-render-return': require('./rules/require-render-return'),
'require-slots-as-functions': require('./rules/require-slots-as-functions'),
'require-toggle-inside-transition': require('./rules/require-toggle-inside-transition'),
'require-typed-object-prop': require('./rules/require-typed-object-prop'),
'require-typed-ref': require('./rules/require-typed-ref'),
'require-v-for-key': require('./rules/require-v-for-key'),
'require-valid-default-prop': require('./rules/require-valid-default-prop'),
Expand Down Expand Up @@ -253,7 +254,7 @@
'.vue': require('./processor')
},
environments: {
// TODO Remove in the next major version

Check warning on line 257 in lib/index.js

View workflow job for this annotation

GitHub Actions / Lint

Unexpected 'todo' comment: 'TODO Remove in the next major version'
/** @deprecated */
'setup-compiler-macros': {
globals: {
Expand Down
150 changes: 150 additions & 0 deletions lib/rules/require-typed-object-prop.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/**
* @author Przemysław Jan Beigert
* See LICENSE file in root directory for full license.
*/
'use strict'

const utils = require('../utils')

/**
* @param {RuleContext} context
* @param {Identifier} identifierNode
*/
const checkPropIdentifierType = (context, identifierNode) => {
if (identifierNode.name === 'Object' || identifierNode.name === 'Array') {
const arrayTypeSuggestion = identifierNode.name === 'Array' ? '[]' : ''
context.report({
node: identifierNode,
messageId: 'expectedTypeAnnotation',
suggest: [
{
messageId: 'addTypeAnnotation',
data: { type: `any${arrayTypeSuggestion}` },
fix(fixer) {
return fixer.insertTextAfter(
identifierNode,
` as PropType<any${arrayTypeSuggestion}>`
)
}
},
{
messageId: 'addTypeAnnotation',
data: { type: `unknown${arrayTypeSuggestion}` },
fix(fixer) {
return fixer.insertTextAfter(
identifierNode,
` as PropType<unknown${arrayTypeSuggestion}>`
)
}
}
]
})
}
}

/**
* @param {RuleContext} context
* @param {ArrayExpression} arrayNode
*/
const checkPropArrayType = (context, arrayNode) => {
for (const elementNode of arrayNode.elements) {
if (elementNode?.type === 'Identifier') {
checkPropIdentifierType(context, elementNode)
}
}
}

/**
* @param {RuleContext} context
* @param {ObjectExpression} objectNode
*/
const checkPropObjectType = (context, objectNode) => {
const typeProperty = objectNode.properties.find(
(prop) =>
prop.type === 'Property' &&
prop.key.type === 'Identifier' &&
prop.key.name === 'type'
)
if (!typeProperty || typeProperty.type !== 'Property') {
return
}

if (typeProperty.value.type === 'Identifier') {
// `foo: { type: String }`
checkPropIdentifierType(context, typeProperty.value)
} else if (typeProperty.value.type === 'ArrayExpression') {
// `foo: { type: [String, Boolean] }`
checkPropArrayType(context, typeProperty.value)
}
}

/**
* @param {import('../utils').ComponentProp} prop
* @param {RuleContext} context
*/
const checkProp = (prop, context) => {
if (prop.type !== 'object') {
return
}

switch (prop.node.value.type) {
case 'Identifier': {
// e.g. `foo: String`
checkPropIdentifierType(context, prop.node.value)
break
}
case 'ArrayExpression': {
// e.g. `foo: [String, Boolean]`
checkPropArrayType(context, prop.node.value)
break
}
case 'ObjectExpression': {
// e.g. `foo: { type: … }`
checkPropObjectType(context, prop.node.value)
return
}
}
}

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

module.exports = {
meta: {
type: 'suggestion',
docs: {
description: 'enforce adding type declarations to object props',
categories: undefined,
recommended: false,
url: 'https://eslint.vuejs.org/rules/require-typed-object-prop.html'
},
fixable: null,
schema: [],
// eslint-disable-next-line eslint-plugin/require-meta-has-suggestions -- `context.report` with suggestion is not recognized in `checkPropIdentifierType`
hasSuggestions: true,
messages: {
expectedTypeAnnotation: 'Expected type annotation on object prop.',
addTypeAnnotation: 'Add `{{ type }}` type annotation.'
}
},
/** @param {RuleContext} context */
create(context) {
return utils.compositingVisitors(
utils.defineScriptSetupVisitor(context, {
onDefinePropsEnter(_node, props) {
for (const prop of props) {
checkProp(prop, context)
}
}
}),
utils.executeOnVue(context, (obj) => {
const props = utils.getComponentPropsFromOptions(obj)

for (const prop of props) {
checkProp(prop, context)
}
})
)
}
}