|
10 | 10 |
|
11 | 11 | const { findVariable } = require('eslint-utils') |
12 | 12 | const utils = require('../utils') |
13 | | -const { isKebabCase } = require('../utils/casing') |
| 13 | +const casing = require('../utils/casing') |
14 | 14 | const { toRegExp } = require('../utils/regexp') |
15 | 15 |
|
16 | 16 | // ------------------------------------------------------------------------------ |
17 | 17 | // Helpers |
18 | 18 | // ------------------------------------------------------------------------------ |
19 | 19 |
|
20 | | -/** |
21 | | - * Check whether the given event name is valid. |
22 | | - * @param {string} name The name to check. |
23 | | - * @returns {boolean} `true` if the given event name is valid. |
24 | | - */ |
25 | | -function isValidEventName(name) { |
26 | | - return isKebabCase(name) || name.startsWith('update:') |
27 | | -} |
| 20 | +const ALLOWED_CASE_OPTIONS = ['kebab-case', 'camelCase'] |
| 21 | +const DEFAULT_CASE = 'kebab-case' |
28 | 22 |
|
29 | 23 | /** |
30 | 24 | * Get the name param node from the given CallExpression |
@@ -64,53 +58,87 @@ function getCalleeMemberNode(node) { |
64 | 58 | // Rule Definition |
65 | 59 | // ------------------------------------------------------------------------------ |
66 | 60 |
|
| 61 | +const OBJECT_OPTION_SCHEMA = { |
| 62 | + type: 'object', |
| 63 | + properties: { |
| 64 | + ignores: { |
| 65 | + type: 'array', |
| 66 | + items: { type: 'string' }, |
| 67 | + uniqueItems: true, |
| 68 | + additionalItems: false |
| 69 | + } |
| 70 | + }, |
| 71 | + additionalProperties: false |
| 72 | +} |
67 | 73 | module.exports = { |
68 | 74 | meta: { |
69 | 75 | type: 'suggestion', |
70 | 76 | docs: { |
71 | | - description: 'enforce custom event names always use "kebab-case"', |
72 | | - categories: ['vue3-essential', 'essential'], |
| 77 | + description: 'enforce specific casing for custom event name', |
| 78 | + categories: undefined, |
73 | 79 | url: 'https://eslint.vuejs.org/rules/custom-event-name-casing.html' |
74 | 80 | }, |
75 | 81 | fixable: null, |
76 | | - schema: [ |
77 | | - { |
78 | | - type: 'object', |
79 | | - properties: { |
80 | | - ignores: { |
81 | | - type: 'array', |
82 | | - items: { type: 'string' }, |
83 | | - uniqueItems: true, |
84 | | - additionalItems: false |
85 | | - } |
| 82 | + schema: { |
| 83 | + anyOf: [ |
| 84 | + { |
| 85 | + type: 'array', |
| 86 | + items: [ |
| 87 | + { |
| 88 | + enum: ALLOWED_CASE_OPTIONS |
| 89 | + }, |
| 90 | + OBJECT_OPTION_SCHEMA |
| 91 | + ] |
86 | 92 | }, |
87 | | - additionalProperties: false |
88 | | - } |
89 | | - ], |
| 93 | + // For backward compatibility |
| 94 | + { |
| 95 | + type: 'array', |
| 96 | + items: [OBJECT_OPTION_SCHEMA] |
| 97 | + } |
| 98 | + ] |
| 99 | + }, |
90 | 100 | messages: { |
91 | | - unexpected: "Custom event name '{{name}}' must be kebab-case." |
| 101 | + unexpected: "Custom event name '{{name}}' must be {{caseType}}." |
92 | 102 | } |
93 | 103 | }, |
94 | 104 | /** @param {RuleContext} context */ |
95 | 105 | create(context) { |
| 106 | + /** @type {Map<ObjectExpression, {contextReferenceIds:Set<Identifier>,emitReferenceIds:Set<Identifier>}>} */ |
96 | 107 | const setupContexts = new Map() |
97 | | - const options = context.options[0] || {} |
| 108 | + const options = |
| 109 | + context.options.length === 1 && typeof context.options[0] !== 'string' |
| 110 | + ? // For backward compatibility |
| 111 | + [undefined, context.options[0]] |
| 112 | + : context.options |
| 113 | + const caseType = options[0] || DEFAULT_CASE |
| 114 | + const objectOption = options[1] || {} |
| 115 | + const caseChecker = casing.getChecker(caseType) |
98 | 116 | /** @type {RegExp[]} */ |
99 | | - const ignores = (options.ignores || []).map(toRegExp) |
| 117 | + const ignores = (objectOption.ignores || []).map(toRegExp) |
| 118 | + |
| 119 | + /** |
| 120 | + * Check whether the given event name is valid. |
| 121 | + * @param {string} name The name to check. |
| 122 | + * @returns {boolean} `true` if the given event name is valid. |
| 123 | + */ |
| 124 | + function isValidEventName(name) { |
| 125 | + return caseChecker(name) || name.startsWith('update:') |
| 126 | + } |
100 | 127 |
|
101 | 128 | /** |
102 | 129 | * @param { Literal & { value: string } } nameLiteralNode |
103 | 130 | */ |
104 | 131 | function verify(nameLiteralNode) { |
105 | 132 | const name = nameLiteralNode.value |
106 | | - if (ignores.some((re) => re.test(name)) || isValidEventName(name)) { |
| 133 | + if (isValidEventName(name) || ignores.some((re) => re.test(name))) { |
107 | 134 | return |
108 | 135 | } |
109 | 136 | context.report({ |
110 | 137 | node: nameLiteralNode, |
111 | 138 | messageId: 'unexpected', |
112 | 139 | data: { |
113 | | - name |
| 140 | + name, |
| 141 | + caseType |
114 | 142 | } |
115 | 143 | }) |
116 | 144 | } |
@@ -190,14 +218,18 @@ module.exports = { |
190 | 218 | const setupContext = setupContexts.get(vueNode) |
191 | 219 | if (setupContext) { |
192 | 220 | const { contextReferenceIds, emitReferenceIds } = setupContext |
193 | | - if (emitReferenceIds.has(node.callee)) { |
| 221 | + if ( |
| 222 | + node.callee.type === 'Identifier' && |
| 223 | + emitReferenceIds.has(node.callee) |
| 224 | + ) { |
194 | 225 | // verify setup(props,{emit}) {emit()} |
195 | 226 | verify(nameLiteralNode) |
196 | 227 | } else { |
197 | 228 | const emit = getCalleeMemberNode(node) |
198 | 229 | if ( |
199 | 230 | emit && |
200 | 231 | emit.name === 'emit' && |
| 232 | + emit.member.object.type === 'Identifier' && |
201 | 233 | contextReferenceIds.has(emit.member.object) |
202 | 234 | ) { |
203 | 235 | // verify setup(props,context) {context.emit()} |
|
0 commit comments