Skip to content

Commit 255db96

Browse files
authored
Merge branch 'master' into fix/ignore-attribute-binding
2 parents 62f749b + 50bde65 commit 255db96

File tree

5 files changed

+222
-29
lines changed

5 files changed

+222
-29
lines changed

lib/rules/custom-event-name-casing.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ module.exports = {
101101
create(context) {
102102
/** @type {Map<ObjectExpression|Program, {contextReferenceIds:Set<Identifier>,emitReferenceIds:Set<Identifier>}>} */
103103
const setupContexts = new Map()
104+
let emitParamName = ''
104105
const options =
105106
context.options.length === 1 && typeof context.options[0] !== 'string'
106107
? // For backward compatibility
@@ -189,7 +190,11 @@ module.exports = {
189190
// cannot check
190191
return
191192
}
192-
if (callee.type === 'Identifier' && callee.name === '$emit') {
193+
194+
if (
195+
callee.type === 'Identifier' &&
196+
(callee.name === '$emit' || callee.name === emitParamName)
197+
) {
193198
verify(nameWithLoc)
194199
}
195200
}
@@ -209,6 +214,8 @@ module.exports = {
209214
if (emitParam.type !== 'Identifier') {
210215
return
211216
}
217+
emitParamName = emitParam.name
218+
212219
// const emit = defineEmits()
213220
const variable = findVariable(
214221
utils.getScope(context, emitParam),

lib/rules/require-valid-default-prop.js

Lines changed: 66 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ module.exports = {
230230
}
231231

232232
/**
233-
* @param {*} node
233+
* @param {Expression} node
234234
* @param {ComponentObjectProp | ComponentTypeProp | ComponentInferTypeProp} prop
235235
* @param {Iterable<string>} expectedTypeNames
236236
*/
@@ -249,17 +249,22 @@ module.exports = {
249249
})
250250
}
251251

252+
/**
253+
* @typedef {object} DefaultDefine
254+
* @property {Expression} expression
255+
* @property {'assignment'|'withDefaults'|'defaultProperty'} src
256+
*/
252257
/**
253258
* @param {(ComponentObjectProp | ComponentTypeProp | ComponentInferTypeProp)[]} props
254-
* @param {(propName: string) => Expression[]} otherDefaultProvider
259+
* @param {(propName: string) => Iterable<DefaultDefine>} otherDefaultProvider
255260
*/
256261
function processPropDefs(props, otherDefaultProvider) {
257262
/** @type {PropDefaultFunctionContext[]} */
258263
const propContexts = []
259264
for (const prop of props) {
260265
let typeList
261-
/** @type {Expression[]} */
262-
const defExprList = []
266+
/** @type {DefaultDefine[]} */
267+
const defaultList = []
263268
if (prop.type === 'object') {
264269
if (prop.value.type === 'ObjectExpression') {
265270
const type = getPropertyNode(prop.value, 'type')
@@ -268,36 +273,44 @@ module.exports = {
268273
typeList = getTypes(type.value)
269274

270275
const def = getPropertyNode(prop.value, 'default')
271-
if (!def) continue
272-
273-
defExprList.push(def.value)
276+
if (def) {
277+
defaultList.push({
278+
src: 'defaultProperty',
279+
expression: def.value
280+
})
281+
}
274282
} else {
275283
typeList = getTypes(prop.value)
276284
}
277285
} else {
278286
typeList = prop.types
279287
}
280288
if (prop.propName != null) {
281-
defExprList.push(...otherDefaultProvider(prop.propName))
289+
defaultList.push(...otherDefaultProvider(prop.propName))
282290
}
283291

284-
if (defExprList.length === 0) continue
292+
if (defaultList.length === 0) continue
285293

286294
const typeNames = new Set(
287295
typeList.filter((item) => NATIVE_TYPES.has(item))
288296
)
289297
// There is no native types detected
290298
if (typeNames.size === 0) continue
291299

292-
for (const defExpr of defExprList) {
293-
const defType = getValueType(defExpr)
300+
for (const defaultDef of defaultList) {
301+
const defType = getValueType(defaultDef.expression)
294302

295303
if (!defType) continue
296304

297305
if (defType.function) {
298306
if (typeNames.has('Function')) {
299307
continue
300308
}
309+
if (defaultDef.src === 'assignment') {
310+
// Factory functions cannot be used in default definitions with initial value assignments.
311+
report(defaultDef.expression, prop, typeNames)
312+
continue
313+
}
301314
if (defType.expression) {
302315
if (!defType.returnType || typeNames.has(defType.returnType)) {
303316
continue
@@ -311,18 +324,23 @@ module.exports = {
311324
})
312325
}
313326
} else {
314-
if (
315-
typeNames.has(defType.type) &&
316-
!FUNCTION_VALUE_TYPES.has(defType.type)
317-
) {
318-
continue
327+
if (typeNames.has(defType.type)) {
328+
if (defaultDef.src === 'assignment') {
329+
continue
330+
}
331+
if (!FUNCTION_VALUE_TYPES.has(defType.type)) {
332+
// For Array and Object, defaults must be defined in the factory function.
333+
continue
334+
}
319335
}
320336
report(
321-
defExpr,
337+
defaultDef.expression,
322338
prop,
323-
[...typeNames].map((type) =>
324-
FUNCTION_VALUE_TYPES.has(type) ? 'Function' : type
325-
)
339+
defaultDef.src === 'assignment'
340+
? typeNames
341+
: [...typeNames].map((type) =>
342+
FUNCTION_VALUE_TYPES.has(type) ? 'Function' : type
343+
)
326344
)
327345
}
328346
}
@@ -425,12 +443,19 @@ module.exports = {
425443
utils.getWithDefaultsPropExpressions(node)
426444
const defaultsByAssignmentPatterns =
427445
utils.getDefaultPropExpressionsForPropsDestructure(node)
428-
const propContexts = processPropDefs(props, (propName) =>
429-
[
430-
defaultsByWithDefaults[propName],
431-
defaultsByAssignmentPatterns[propName]?.expression
432-
].filter(utils.isDef)
433-
)
446+
const propContexts = processPropDefs(props, function* (propName) {
447+
const withDefaults = defaultsByWithDefaults[propName]
448+
if (withDefaults) {
449+
yield { src: 'withDefaults', expression: withDefaults }
450+
}
451+
const assignmentPattern = defaultsByAssignmentPatterns[propName]
452+
if (assignmentPattern) {
453+
yield {
454+
src: 'assignment',
455+
expression: assignmentPattern.expression
456+
}
457+
}
458+
})
434459
scriptSetupPropsContexts.push({ node, props: propContexts })
435460
},
436461
/**
@@ -450,7 +475,21 @@ module.exports = {
450475
}
451476
},
452477
onDefinePropsExit() {
453-
scriptSetupPropsContexts.pop()
478+
const data = scriptSetupPropsContexts.pop()
479+
if (!data) {
480+
return
481+
}
482+
for (const {
483+
prop,
484+
types: typeNames,
485+
default: defType
486+
} of data.props) {
487+
for (const returnType of defType.returnTypes) {
488+
if (typeNames.has(returnType.type)) continue
489+
490+
report(returnType.node, prop, typeNames)
491+
}
492+
}
454493
}
455494
})
456495
)

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "eslint-plugin-vue",
3-
"version": "9.29.1",
3+
"version": "9.30.0",
44
"description": "Official ESLint plugin for Vue.js",
55
"main": "lib/index.js",
66
"types": "lib/index.d.ts",

tests/lib/rules/custom-event-name-casing.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,22 @@ tester.run('custom-event-name-casing', rule, {
329329
</script>
330330
`,
331331
options: ['kebab-case']
332+
},
333+
// setup defineEmits
334+
{
335+
filename: 'test.vue',
336+
code: `
337+
<script setup>
338+
const emit = defineEmits({})
339+
emit('foo-bar')
340+
</script>
341+
342+
<template>
343+
<button @click="emit('foo-bar')">Foo</button>
344+
<button @click="$emit('foo-bar')">Foo</button>
345+
</template>
346+
`,
347+
options: ['kebab-case']
332348
}
333349
],
334350
invalid: [
@@ -605,6 +621,35 @@ tester.run('custom-event-name-casing', rule, {
605621
line: 4
606622
}
607623
]
624+
},
625+
{
626+
// https://github.com/vuejs/eslint-plugin-vue/issues/2577
627+
filename: 'test.vue',
628+
code: `
629+
<script setup>
630+
const emit = defineEmits({})
631+
emit('foo-bar')
632+
</script>
633+
634+
<template>
635+
<button @click="emit('foo-bar')">Foo</button>
636+
<button @click="$emit('foo-bar')">Foo</button>
637+
</template>
638+
`,
639+
errors: [
640+
{
641+
message: "Custom event name 'foo-bar' must be camelCase.",
642+
line: 4
643+
},
644+
{
645+
message: "Custom event name 'foo-bar' must be camelCase.",
646+
line: 8
647+
},
648+
{
649+
message: "Custom event name 'foo-bar' must be camelCase.",
650+
line: 9
651+
}
652+
]
608653
}
609654
]
610655
})

tests/lib/rules/require-valid-default-prop.js

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,21 @@ ruleTester.run('require-valid-default-prop', rule, {
316316
languageOptions: {
317317
parser: require('vue-eslint-parser')
318318
}
319+
},
320+
{
321+
filename: 'test.vue',
322+
code: `
323+
<script setup lang="ts">
324+
const { foo = [] } = defineProps({
325+
foo: {
326+
type: Array,
327+
}
328+
})
329+
</script>
330+
`,
331+
languageOptions: {
332+
parser: require('vue-eslint-parser')
333+
}
319334
}
320335
],
321336

@@ -1098,6 +1113,93 @@ ruleTester.run('require-valid-default-prop', rule, {
10981113
line: 6
10991114
}
11001115
]
1116+
},
1117+
{
1118+
filename: 'test.vue',
1119+
code: `
1120+
<script setup>
1121+
const { foo = [] } = defineProps({
1122+
foo: {
1123+
type: Number,
1124+
}
1125+
})
1126+
</script>
1127+
`,
1128+
languageOptions: {
1129+
parser: require('vue-eslint-parser')
1130+
},
1131+
errors: [
1132+
{
1133+
message: "Type of the default value for 'foo' prop must be a number.",
1134+
line: 3
1135+
}
1136+
]
1137+
},
1138+
{
1139+
filename: 'test.vue',
1140+
code: `
1141+
<script setup>
1142+
const { foo = 42 } = defineProps({
1143+
foo: {
1144+
type: Array,
1145+
}
1146+
})
1147+
</script>
1148+
`,
1149+
languageOptions: {
1150+
parser: require('vue-eslint-parser')
1151+
},
1152+
errors: [
1153+
{
1154+
message: "Type of the default value for 'foo' prop must be a array.",
1155+
line: 3
1156+
}
1157+
]
1158+
},
1159+
{
1160+
filename: 'test.vue',
1161+
code: `
1162+
<script setup>
1163+
const { foo = [] } = defineProps({
1164+
foo: {
1165+
type: Array,
1166+
default: () => {
1167+
return 42
1168+
}
1169+
}
1170+
})
1171+
</script>
1172+
`,
1173+
languageOptions: {
1174+
parser: require('vue-eslint-parser')
1175+
},
1176+
errors: [
1177+
{
1178+
message: "Type of the default value for 'foo' prop must be a array.",
1179+
line: 7
1180+
}
1181+
]
1182+
},
1183+
{
1184+
filename: 'test.vue',
1185+
code: `
1186+
<script setup>
1187+
const { foo = (()=>[]) } = defineProps({
1188+
foo: {
1189+
type: Array,
1190+
}
1191+
})
1192+
</script>
1193+
`,
1194+
languageOptions: {
1195+
parser: require('vue-eslint-parser')
1196+
},
1197+
errors: [
1198+
{
1199+
message: "Type of the default value for 'foo' prop must be a array.",
1200+
line: 3
1201+
}
1202+
]
11011203
}
11021204
]
11031205
})

0 commit comments

Comments
 (0)