Skip to content

Commit d8f72ba

Browse files
authored
Merge branch 'vuejs:master' into master
2 parents 21dfb40 + 2dc606c commit d8f72ba

15 files changed

+864
-245
lines changed

docs/user-guide/index.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,16 @@ Vue.component('AsyncComponent', (resolve, reject) => {
221221
})
222222
```
223223

224+
You can do this for [Vue class component](https://class-component.vuejs.org/) too:
225+
226+
```ts
227+
// @vue/component
228+
@Component({
229+
components: { Foo }
230+
})
231+
export default class Bar extends Vue {}
232+
```
233+
224234
### Disabling rules via `<!-- eslint-disable -->`
225235

226236
You can use `<!-- eslint-disable -->`-like HTML comments in the `<template>` and in the block level of `.vue` files to disable a certain rule temporarily.

lib/rules/no-boolean-default.js

Lines changed: 43 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,27 @@ const utils = require('../utils')
88

99
/**
1010
* @typedef {import('../utils').ComponentProp} ComponentProp
11+
* @typedef {import('../utils').ComponentObjectProp} ComponentObjectProp
1112
*/
1213

1314
/**
14-
* @param {Property | SpreadElement} prop
15+
* @param {Expression|undefined} node
16+
*/
17+
function isBooleanIdentifier(node) {
18+
return Boolean(node && node.type === 'Identifier' && node.name === 'Boolean')
19+
}
20+
21+
/**
22+
* Detects whether given prop node is a Boolean
23+
* @param {ComponentObjectProp} prop
24+
* @return {Boolean}
1525
*/
1626
function isBooleanProp(prop) {
27+
const value = utils.skipTSAsExpression(prop.value)
1728
return (
18-
prop.type === 'Property' &&
19-
prop.key.type === 'Identifier' &&
20-
prop.key.name === 'type' &&
21-
prop.value.type === 'Identifier' &&
22-
prop.value.name === 'Boolean'
29+
isBooleanIdentifier(value) ||
30+
(value.type === 'ObjectExpression' &&
31+
isBooleanIdentifier(utils.findProperty(value, 'type')?.value))
2332
)
2433
}
2534

@@ -55,40 +64,40 @@ module.exports = {
5564
const booleanType = context.options[0] || 'no-default'
5665
/**
5766
* @param {ComponentProp} prop
58-
* @param { { [key: string]: Expression | undefined } } [withDefaultsExpressions]
67+
* @param {(propName: string) => Expression[]} otherDefaultProvider
5968
*/
60-
function processProp(prop, withDefaultsExpressions) {
69+
function processProp(prop, otherDefaultProvider) {
6170
if (prop.type === 'object') {
62-
if (prop.value.type !== 'ObjectExpression') {
71+
if (!isBooleanProp(prop)) {
6372
return
6473
}
65-
if (!prop.value.properties.some(isBooleanProp)) {
66-
return
74+
if (prop.value.type === 'ObjectExpression') {
75+
const defaultNode = getDefaultNode(prop.value)
76+
if (defaultNode) {
77+
verifyDefaultExpression(defaultNode.value)
78+
}
6779
}
68-
const defaultNode = getDefaultNode(prop.value)
69-
if (!defaultNode) {
70-
return
80+
if (prop.propName != null) {
81+
for (const defaultNode of otherDefaultProvider(prop.propName)) {
82+
verifyDefaultExpression(defaultNode)
83+
}
7184
}
72-
verifyDefaultExpression(defaultNode.value)
7385
} else if (prop.type === 'type') {
7486
if (prop.types.length !== 1 || prop.types[0] !== 'Boolean') {
7587
return
7688
}
77-
const defaultNode =
78-
withDefaultsExpressions && withDefaultsExpressions[prop.propName]
79-
if (!defaultNode) {
80-
return
89+
for (const defaultNode of otherDefaultProvider(prop.propName)) {
90+
verifyDefaultExpression(defaultNode)
8191
}
82-
verifyDefaultExpression(defaultNode)
8392
}
8493
}
8594
/**
8695
* @param {ComponentProp[]} props
87-
* @param { { [key: string]: Expression | undefined } } [withDefaultsExpressions]
96+
* @param {(propName: string) => Expression[]} otherDefaultProvider
8897
*/
89-
function processProps(props, withDefaultsExpressions) {
98+
function processProps(props, otherDefaultProvider) {
9099
for (const prop of props) {
91-
processProp(prop, withDefaultsExpressions)
100+
processProp(prop, otherDefaultProvider)
92101
}
93102
}
94103

@@ -118,11 +127,20 @@ module.exports = {
118127
}
119128
return utils.compositingVisitors(
120129
utils.executeOnVueComponent(context, (obj) => {
121-
processProps(utils.getComponentPropsFromOptions(obj))
130+
processProps(utils.getComponentPropsFromOptions(obj), () => [])
122131
}),
123132
utils.defineScriptSetupVisitor(context, {
124133
onDefinePropsEnter(node, props) {
125-
processProps(props, utils.getWithDefaultsPropExpressions(node))
134+
const defaultsByWithDefaults =
135+
utils.getWithDefaultsPropExpressions(node)
136+
const defaultsByAssignmentPatterns =
137+
utils.getDefaultPropExpressionsForPropsDestructure(node)
138+
processProps(props, (propName) =>
139+
[
140+
defaultsByWithDefaults[propName],
141+
defaultsByAssignmentPatterns[propName]?.expression
142+
].filter(utils.isDef)
143+
)
126144
}
127145
})
128146
)

lib/rules/no-required-prop-with-default.js

Lines changed: 51 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,16 @@ module.exports = {
4747
}
4848

4949
/**
50-
* @param {ComponentArrayProp | ComponentObjectProp | ComponentUnknownProp | ComponentProp} prop
51-
* */
52-
const handleObjectProp = (prop) => {
50+
* @param {ComponentProp} prop
51+
* @param {Set<string>} [defaultProps]
52+
**/
53+
const handleObjectProp = (prop, defaultProps) => {
5354
if (
5455
prop.type === 'object' &&
5556
prop.propName &&
5657
prop.value.type === 'ObjectExpression' &&
57-
utils.findProperty(prop.value, 'default')
58+
(utils.findProperty(prop.value, 'default') ||
59+
defaultProps?.has(prop.propName))
5860
) {
5961
const requiredProperty = utils.findProperty(prop.value, 'required')
6062
if (!requiredProperty) return
@@ -84,62 +86,61 @@ module.exports = {
8486
]
8587
})
8688
}
89+
} else if (
90+
prop.type === 'type' &&
91+
defaultProps?.has(prop.propName) &&
92+
prop.required
93+
) {
94+
// skip setter & getter case
95+
if (
96+
prop.node.type === 'TSMethodSignature' &&
97+
(prop.node.kind === 'get' || prop.node.kind === 'set')
98+
) {
99+
return
100+
}
101+
// skip computed
102+
if (prop.node.computed) {
103+
return
104+
}
105+
context.report({
106+
node: prop.node,
107+
loc: prop.node.loc,
108+
data: {
109+
key: prop.propName
110+
},
111+
messageId: 'requireOptional',
112+
fix: canAutoFix
113+
? (fixer) => fixer.insertTextAfter(prop.key, '?')
114+
: null,
115+
suggest: canAutoFix
116+
? null
117+
: [
118+
{
119+
messageId: 'fixRequiredProp',
120+
fix: (fixer) => fixer.insertTextAfter(prop.key, '?')
121+
}
122+
]
123+
})
87124
}
88125
}
89126

90127
return utils.compositingVisitors(
91128
utils.defineVueVisitor(context, {
92129
onVueObjectEnter(node) {
93-
utils.getComponentPropsFromOptions(node).map(handleObjectProp)
130+
utils
131+
.getComponentPropsFromOptions(node)
132+
.map((prop) => handleObjectProp(prop))
94133
}
95134
}),
96135
utils.defineScriptSetupVisitor(context, {
97136
onDefinePropsEnter(node, props) {
98-
if (!utils.hasWithDefaults(node)) {
99-
props.map(handleObjectProp)
100-
return
101-
}
102-
const withDefaultsProps = Object.keys(
103-
utils.getWithDefaultsPropExpressions(node)
104-
)
105-
const requiredProps = props.flatMap((item) =>
106-
item.type === 'type' && item.required ? [item] : []
107-
)
108-
109-
for (const prop of requiredProps) {
110-
if (withDefaultsProps.includes(prop.propName)) {
111-
// skip setter & getter case
112-
if (
113-
prop.node.type === 'TSMethodSignature' &&
114-
(prop.node.kind === 'get' || prop.node.kind === 'set')
115-
) {
116-
return
117-
}
118-
// skip computed
119-
if (prop.node.computed) {
120-
return
121-
}
122-
context.report({
123-
node: prop.node,
124-
loc: prop.node.loc,
125-
data: {
126-
key: prop.propName
127-
},
128-
messageId: 'requireOptional',
129-
fix: canAutoFix
130-
? (fixer) => fixer.insertTextAfter(prop.key, '?')
131-
: null,
132-
suggest: canAutoFix
133-
? null
134-
: [
135-
{
136-
messageId: 'fixRequiredProp',
137-
fix: (fixer) => fixer.insertTextAfter(prop.key, '?')
138-
}
139-
]
140-
})
141-
}
142-
}
137+
const defaultProps = new Set([
138+
...Object.keys(utils.getWithDefaultsPropExpressions(node)),
139+
...Object.keys(
140+
utils.getDefaultPropExpressionsForPropsDestructure(node)
141+
)
142+
])
143+
props.map((prop) => handleObjectProp(prop, defaultProps))
143144
}
144145
})
145146
)

lib/rules/no-restricted-props.js

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,9 @@ module.exports = {
9595

9696
/**
9797
* @param {ComponentProp[]} props
98-
* @param { { [key: string]: Property | undefined } } [withDefaultsProps]
98+
* @param {(fixer: RuleFixer, propName: string, replaceKeyText: string) => Iterable<Fix>} [fixPropInOtherPlaces]
9999
*/
100-
function processProps(props, withDefaultsProps) {
100+
function processProps(props, fixPropInOtherPlaces) {
101101
for (const prop of props) {
102102
if (!prop.propName) {
103103
continue
@@ -118,7 +118,14 @@ module.exports = {
118118
: createSuggest(
119119
prop.key,
120120
option,
121-
withDefaultsProps && withDefaultsProps[prop.propName]
121+
fixPropInOtherPlaces
122+
? (fixer, replaceKeyText) =>
123+
fixPropInOtherPlaces(
124+
fixer,
125+
prop.propName,
126+
replaceKeyText
127+
)
128+
: undefined
122129
)
123130
})
124131
break
@@ -129,7 +136,33 @@ module.exports = {
129136
return utils.compositingVisitors(
130137
utils.defineScriptSetupVisitor(context, {
131138
onDefinePropsEnter(node, props) {
132-
processProps(props, utils.getWithDefaultsProps(node))
139+
processProps(props, fixPropInOtherPlaces)
140+
141+
/**
142+
* @param {RuleFixer} fixer
143+
* @param {string} propName
144+
* @param {string} replaceKeyText
145+
*/
146+
function fixPropInOtherPlaces(fixer, propName, replaceKeyText) {
147+
/** @type {(Property|AssignmentProperty)[]} */
148+
const propertyNodes = []
149+
const withDefault = utils.getWithDefaultsProps(node)[propName]
150+
if (withDefault) {
151+
propertyNodes.push(withDefault)
152+
}
153+
const propDestructure = utils.getPropsDestructure(node)[propName]
154+
if (propDestructure) {
155+
propertyNodes.push(propDestructure)
156+
}
157+
return propertyNodes.map((propertyNode) =>
158+
propertyNode.shorthand
159+
? fixer.insertTextBefore(
160+
propertyNode.value,
161+
`${replaceKeyText}:`
162+
)
163+
: fixer.replaceText(propertyNode.key, replaceKeyText)
164+
)
165+
}
133166
}
134167
}),
135168
utils.defineVueVisitor(context, {
@@ -144,10 +177,10 @@ module.exports = {
144177
/**
145178
* @param {Expression} node
146179
* @param {ParsedOption} option
147-
* @param {Property} [withDefault]
180+
* @param {(fixer: RuleFixer, replaceKeyText: string) => Iterable<Fix>} [fixPropInOtherPlaces]
148181
* @returns {Rule.SuggestionReportDescriptor[]}
149182
*/
150-
function createSuggest(node, option, withDefault) {
183+
function createSuggest(node, option, fixPropInOtherPlaces) {
151184
if (!option.suggest) {
152185
return []
153186
}
@@ -168,14 +201,8 @@ function createSuggest(node, option, withDefault) {
168201
{
169202
fix(fixer) {
170203
const fixes = [fixer.replaceText(node, replaceText)]
171-
if (withDefault) {
172-
if (withDefault.shorthand) {
173-
fixes.push(
174-
fixer.insertTextBefore(withDefault.value, `${replaceText}:`)
175-
)
176-
} else {
177-
fixes.push(fixer.replaceText(withDefault.key, replaceText))
178-
}
204+
if (fixPropInOtherPlaces) {
205+
fixes.push(...fixPropInOtherPlaces(fixer, replaceText))
179206
}
180207
return fixes.sort((a, b) => a.range[0] - b.range[0])
181208
},

0 commit comments

Comments
 (0)