Skip to content

Add vue/require-typed-object-prop rule #1983

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

Merged
Merged
Show file tree
Hide file tree
Changes from 9 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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions docs/rules/force-types-on-object-props.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
---
pageClass: rule-details
sidebarDepth: 0
title: vue/force-types-on-object-props
description: enforce user to add type declaration to object props
---
# vue/force-types-on-object-props

> enforce user to add type declaration 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"`.

## :book: Rule Details

Prevent missing type declaration of not primitive objects in a TypeScript projects.

Bad:

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

```js
export default {
props: {
prop: {
type: Object,
},
},
}
```

</eslint-code-block>

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

```ts
export default {
props: {
prop: {
type: Array
}
}
}
```

</eslint-code-block>

Good:

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

```ts
export default {
props: {
prop: {
type: Object as Props<Anything>,
}
}
}
```

</eslint-code-block>

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

```ts
export default {
props: {
prop: {
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 user to add type declaration 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/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
119 changes: 119 additions & 0 deletions lib/rules/force-types-on-object-props.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/**
* @author Przemysław Jan Beigert
* See LICENSE file in root directory for full license.
*/
'use strict'

// ------------------------------------------------------------------------------
// 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)
})

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

module.exports = {
meta: {
type: 'suggestion',
docs: {
description: 'enforce user to add type declaration to object props',
categories: ['base'],
recommended: false,
url: 'https://eslint.vuejs.org/rules/force-types-on-object-props.html'
},
fixable: null,
schema: []
},
/** @param {RuleContext} context */
create(context) {
return {
/** @param {ExportDefaultDeclaration} node */
ExportDefaultDeclaration(node) {
if (node.declaration.type !== 'ObjectExpression') {
return
}
if (!Array.isArray(node.declaration.properties)) {
return
}

const property = node.declaration.properties.find(
(property) =>
property.type === 'Property' &&
isLooksLike(property.key, { type: 'Identifier', name: 'props' }) &&
property.value.type === 'ObjectExpression'
)

if (
!property ||
property.type === 'SpreadElement' ||
!('properties' in property.value)
) {
return
}
const properties = property.value.properties
.filter(
(prop) =>
prop.type === 'Property' && prop.value.type === 'ObjectExpression'
)
.map((prop) =>
prop.value.properties.find((propValueProperty) =>
isLooksLike(propValueProperty.key, {
type: 'Identifier',
name: 'type'
})
)
)
for (const prop of properties) {
if (!prop) {
continue
}
if (isLooksLike(prop.value, { type: 'Identifier', name: 'Object' })) {
context.report({
node: prop,
message: 'Object props has to contains type.'
})
}
if (prop.value.type === 'TSAsExpression') {
const { typeAnnotation } = prop.value
if (
[
'TSAnyKeyword',
'TSTypeLiteral',
'TSUnknownKeyword',
'TSObjectKeyword'
].includes(typeAnnotation.type) ||
!typeAnnotation.typeName ||
typeAnnotation.typeName.name !== 'Prop'
) {
context.report({
node: prop,
message: 'Object props has to contains type.'
})
}
}
}
}
}
}
}
95 changes: 95 additions & 0 deletions tests/lib/rules/force-types-on-object-props.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/**
* @author *****your name*****
* See LICENSE file in root directory for full license.
*/
'use strict'

const RuleTester = require('eslint').RuleTester
const rule = require('../../../lib/rules/force-types-on-object-props')

const template = (prop) => `
<script lang="ts">
import { Prop } from 'vue/types/options';
export default {
props: {
prop: {
${prop}
}
}
}
</script>
`

const ruleTester = new RuleTester({
parser: require.resolve('vue-eslint-parser'),
parserOptions: {
ecmaVersion: 2015,
sourceType: 'module',
parser: '@typescript-eslint/parser'
}
})

ruleTester.run('force-types-on-object-props', rule, {
valid: [
`
<script lang="ts">
export default {
}
</script>
`,
`
<script lang="ts">
export default {
props: {}
}
</script>
`,
template('type: Object as Prop<{}>'),
template('type: String'),
template('type: Number'),
template('type: Boolean'),
template('type: [String, Number, Boolean]')
],
invalid: [
{
code: template('type: Object'),
errors: [
{
message: 'Object props has to contains type.'
}
]
},
{
code: template('type: Object as any'),
errors: [
{
message: 'Object props has to contains type.'
}
]
},
{
code: template('type: Object as {}'),
errors: [
{
message: 'Object props has to contains type.'
}
]
},
{
code: template('type: Object as unknown'),
errors: [
{
message: 'Object props has to contains type.'
}
]
},
{
code: template('type: Object as string'),
errors: [
{
message: 'Object props has to contains type.'
}
]
}
]
})