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 14 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
53 changes: 53 additions & 0 deletions docs/rules/force-types-on-object-props.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
---
pageClass: rule-details
sidebarDepth: 0
title: vue/force-types-on-object-props
FloEdelmann marked this conversation as resolved.
Show resolved Hide resolved
description: enforce adding type declarations to object props
---
# vue/force-types-on-object-props

> 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>
- :gear: This rule is included in all of `"plugin:vue/base"`, `"plugin:vue/essential"`, `"plugin:vue/vue3-essential"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/recommended"` and `"plugin:vue/vue3-recommended"`.
FloEdelmann marked this conversation as resolved.
Show resolved Hide resolved

## :book: Rule Details

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

<eslint-code-block :rules="{'vue/force-types-on-object-props': ['error']}">

```ts
export default {
props: {
prop: {
// ✗ BAD
type: Object,
type: Array,

// ✓ GOOD
type: Object as Props<Anything>,
FloEdelmann marked this conversation as resolved.
Show resolved Hide resolved
type: String, // or any other primitive type
}
}
}
```

</eslint-code-block>

### Options

Nothing.

## :mute: When Not To Use It

When you're not using TypeScript in the project.

## :books: Further Reading

Nothing

## :mag: Implementation

- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/force-types-on-object-props.js)
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/force-types-on-object-props.js)
1 change: 1 addition & 0 deletions docs/rules/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Rules in this category are enabled for all presets provided by eslint-plugin-vue
| Rule ID | Description | | |
|:--------|:------------|:--:|:--:|
| [vue/comment-directive](./comment-directive.md) | support comment-directives in `<template>` | | :warning: |
| [vue/force-types-on-object-props](./force-types-on-object-props.md) | enforce adding type declarations to object props | | :hammer: |
| [vue/jsx-uses-vars](./jsx-uses-vars.md) | prevent variables used in JSX to be marked as unused | | :warning: |

</rules-table>
Expand Down
1 change: 1 addition & 0 deletions lib/configs/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ module.exports = {
plugins: ['vue'],
rules: {
'vue/comment-directive': 'error',
'vue/force-types-on-object-props': 'error',
'vue/jsx-uses-vars': 'error'
}
}
1 change: 1 addition & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ module.exports = {
'dot-notation': require('./rules/dot-notation'),
eqeqeq: require('./rules/eqeqeq'),
'first-attribute-linebreak': require('./rules/first-attribute-linebreak'),
'force-types-on-object-props': require('./rules/force-types-on-object-props'),
'func-call-spacing': require('./rules/func-call-spacing'),
'html-button-has-type': require('./rules/html-button-has-type'),
'html-closing-bracket-newline': require('./rules/html-closing-bracket-newline'),
Expand Down
163 changes: 163 additions & 0 deletions lib/rules/force-types-on-object-props.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
/**
* @author Przemysław Jan Beigert
* See LICENSE file in root directory for full license.
*/
'use strict'

const utils = require('../utils')
/**
* @typedef {import('../utils').ComponentProp} ComponentProp
*/

// ------------------------------------------------------------------------------
// Helpers
// ------------------------------------------------------------------------------

/**
* Check if all keys and values from second object are resent in first object
*
* @param {{ [key: string]: any }} a object to
* @param {{ [key: string]: any }} b The string to escape.
* @returns {boolean} Returns the escaped string.
*/
const isLooksLike = (a, b) =>
a &&
b &&
Object.keys(b).every((bKey) => {
const bVal = b[bKey]
const aVal = a[bKey]
if (typeof bVal === 'function') {
return bVal(aVal)
}
return bVal == null || /^[bns]/.test(typeof bVal)
? bVal === aVal
: isLooksLike(aVal, bVal)
})
FloEdelmann marked this conversation as resolved.
Show resolved Hide resolved

/**
* @param {ComponentProp} property
* @param {RuleContext} context
*/
const checkProperty = (property, context) => {
if (!property.value) {
return
}

if (
isLooksLike(property.value, { type: 'Identifier', name: 'Object' }) &&
property.node.value.type !== 'TSAsExpression'
) {
context.report({
node: property.node,
message: 'Expected type annotation on object prop.'
})
}

if (
property.type === 'object' &&
property.value.type === 'ObjectExpression' &&
property.node.value.type === 'ObjectExpression'
) {
const typePropert = property.node.value.properties.find(
FloEdelmann marked this conversation as resolved.
Show resolved Hide resolved
(prop) =>
prop.type === 'Property' &&
prop.key.type === 'Identifier' &&
prop.key.name === 'type'
)
if (
typePropert &&
typePropert.type === 'Property' &&
isLooksLike(typePropert.value, { type: 'Identifier', name: 'Object' })
) {
context.report({
node: property.node,
message: 'Expected type annotation on object prop.'
})
}
}

if (property.node.value.type === 'ObjectExpression') {
for (const prop of property.node.value.properties) {
if (prop.type !== 'Property') {
continue
}
if (prop.key.type !== 'Identifier' || prop.key.name !== 'type') {
continue
}
if (prop.value.type !== 'TSAsExpression') {
continue
}

const { typeAnnotation } = prop.value
if (
['TSAnyKeyword', 'TSUnknownKeyword'].includes(typeAnnotation.type) ||
!typeAnnotation.typeName ||
!['Prop', 'PropType'].includes(typeAnnotation.typeName.name)
) {
context.report({
node: property.node,
message: 'Expected type annotation on object prop.'
})
}
}
}

if (property.node.value.type === 'TSAsExpression') {
const { typeAnnotation } = property.node.value
if (typeAnnotation.type === 'TSFunctionType') {
return
}
if (
[
'TSAnyKeyword',
'TSTypeLiteral',
'TSUnknownKeyword',
'TSObjectKeyword'
].includes(typeAnnotation.type) ||
!typeAnnotation.typeName ||
!['Prop', 'PropType'].includes(typeAnnotation.typeName.name)
) {
context.report({
node: property.node,
message: 'Expected type annotation on object prop.'
})
}
}
}

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

module.exports = {
meta: {
type: 'suggestion',
docs: {
description: 'enforce adding type declarations to object props',
categories: ['base'],
FloEdelmann marked this conversation as resolved.
Show resolved Hide resolved
recommended: false,
url: 'https://eslint.vuejs.org/rules/force-types-on-object-props.html'
},
fixable: null,
schema: []
},
/** @param {RuleContext} context */
create(context) {
return utils.compositingVisitors(
utils.defineScriptSetupVisitor(context, {
onDefinePropsEnter(_node, props) {
for (const prop of props) {
checkProperty(prop, context)
}
}
}),
utils.executeOnVue(context, (obj) => {
const props = utils.getComponentPropsFromOptions(obj)

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