@shrpne/eslint-plugin-vue-extra 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shrpne/eslint-plugin-vue-extra",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Extra ESLint rules for Vue projects",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -27,6 +27,125 @@ function isDefinePropsCall(node) {
27
27
  );
28
28
  }
29
29
 
30
+ function isDefineEmitsCall(node) {
31
+ return (
32
+ node &&
33
+ node.type === 'CallExpression' &&
34
+ node.callee &&
35
+ node.callee.type === 'Identifier' &&
36
+ node.callee.name === 'defineEmits'
37
+ );
38
+ }
39
+
40
+ function getPropNameFromUpdateEventName(eventName) {
41
+ if (typeof eventName !== 'string') return null;
42
+ if (!eventName.startsWith('update:')) return null;
43
+
44
+ const propName = eventName.slice('update:'.length);
45
+ return propName || null;
46
+ }
47
+
48
+ function addModelPropNameFromEventName(set, eventName) {
49
+ const propName = getPropNameFromUpdateEventName(eventName);
50
+ if (propName) {
51
+ set.add(propName);
52
+ }
53
+ }
54
+
55
+ function collectModelPropNamesFromTypeNode(typeNode, modelPropNames) {
56
+ if (!typeNode || typeNode.type !== 'TSTypeLiteral') return;
57
+
58
+ for (const member of typeNode.members) {
59
+ if (member.type === 'TSCallSignatureDeclaration') {
60
+ const firstParam = member.params && member.params[0];
61
+ if (
62
+ firstParam &&
63
+ firstParam.type === 'Identifier' &&
64
+ firstParam.typeAnnotation &&
65
+ firstParam.typeAnnotation.typeAnnotation &&
66
+ firstParam.typeAnnotation.typeAnnotation.type === 'TSLiteralType' &&
67
+ firstParam.typeAnnotation.typeAnnotation.literal &&
68
+ firstParam.typeAnnotation.typeAnnotation.literal.type === 'Literal' &&
69
+ typeof firstParam.typeAnnotation.typeAnnotation.literal.value === 'string'
70
+ ) {
71
+ addModelPropNameFromEventName(
72
+ modelPropNames,
73
+ firstParam.typeAnnotation.typeAnnotation.literal.value
74
+ );
75
+ }
76
+ continue;
77
+ }
78
+
79
+ if (member.type === 'TSPropertySignature' && member.key) {
80
+ if (member.key.type === 'Literal' && typeof member.key.value === 'string') {
81
+ addModelPropNameFromEventName(modelPropNames, member.key.value);
82
+ }
83
+ }
84
+ }
85
+ }
86
+
87
+ function collectModelPropNamesFromDefineEmitsCall(node, modelPropNames) {
88
+ if (!isDefineEmitsCall(node)) return;
89
+
90
+ const firstArg = node.arguments && node.arguments[0];
91
+ if (firstArg && firstArg.type === 'ArrayExpression') {
92
+ for (const element of firstArg.elements) {
93
+ if (element && element.type === 'Literal' && typeof element.value === 'string') {
94
+ addModelPropNameFromEventName(modelPropNames, element.value);
95
+ }
96
+ }
97
+ }
98
+
99
+ if (firstArg && firstArg.type === 'ObjectExpression') {
100
+ for (const prop of firstArg.properties) {
101
+ if (prop.type !== 'Property' || prop.computed || !prop.key) continue;
102
+ if (prop.key.type === 'Literal' && typeof prop.key.value === 'string') {
103
+ addModelPropNameFromEventName(modelPropNames, prop.key.value);
104
+ }
105
+ }
106
+ }
107
+
108
+ const typeArgs = node.typeArguments;
109
+ if (typeArgs && typeArgs.params && typeArgs.params.length > 0) {
110
+ collectModelPropNamesFromTypeNode(typeArgs.params[0], modelPropNames);
111
+ }
112
+ }
113
+
114
+ function collectModelPropNamesFromAst(rootNode) {
115
+ const modelPropNames = new Set(['modelValue']);
116
+ const visited = new WeakSet();
117
+
118
+ function visit(node) {
119
+ if (!node || typeof node !== 'object') return;
120
+ if (visited.has(node)) return;
121
+ visited.add(node);
122
+
123
+ if (node.type === 'CallExpression') {
124
+ collectModelPropNamesFromDefineEmitsCall(node, modelPropNames);
125
+ }
126
+
127
+ for (const value of Object.values(node)) {
128
+ if (!value) continue;
129
+
130
+ if (Array.isArray(value)) {
131
+ for (const item of value) {
132
+ if (item && typeof item.type === 'string') {
133
+ visit(item);
134
+ }
135
+ }
136
+ continue;
137
+ }
138
+
139
+ if (value && typeof value.type === 'string') {
140
+ visit(value);
141
+ }
142
+ }
143
+ }
144
+
145
+ visit(rootNode);
146
+ return modelPropNames;
147
+ }
148
+
30
149
  function isBooleanLikeType(type) {
31
150
  if (!type) return false;
32
151
 
@@ -199,6 +318,7 @@ const preferOptionalBooleanProp = {
199
318
  const services = context.sourceCode.parserServices;
200
319
  const hasTypeInfo = !!(services && services.program && services.esTreeNodeToTSNodeMap);
201
320
  const fixReferencedTypes = !!(context.options[0] && context.options[0].fixReferencedTypes);
321
+ const modelPropNames = collectModelPropNamesFromAst(context.sourceCode.ast);
202
322
 
203
323
  const programTsNode = hasTypeInfo
204
324
  ? services.esTreeNodeToTSNodeMap.get(context.sourceCode.ast)
@@ -226,6 +346,7 @@ const preferOptionalBooleanProp = {
226
346
  const booleanDefaultsMap = getBooleanDefaultsMap(secondArg);
227
347
 
228
348
  for (const [name, metadata] of propMetadataMap.entries()) {
349
+ if (modelPropNames.has(name)) continue;
229
350
  if (!metadata.isBooleanLike) continue;
230
351
  if (metadata.isOptional) continue;
231
352