king-design-analyzer 2.1.6 → 2.1.9
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/dist/ast/index.js +4 -3
- package/dist/ast/index.mjs +2 -1
- package/dist/chunk-2W6OCG2S.js +33356 -0
- package/dist/{chunk-BVQTJJYS.js → chunk-F26GUCGG.js} +2 -2
- package/dist/chunk-H2ET6MMM.mjs +33329 -0
- package/dist/chunk-JSBRDJBE.js +30 -0
- package/dist/{chunk-QSJKP7AO.mjs → chunk-OJOHB64C.mjs} +1 -1
- package/dist/chunk-UJCSKKID.mjs +27 -0
- package/dist/docs/index.js +1 -1
- package/dist/docs/index.mjs +1 -1
- package/dist/full/index.js +5 -4
- package/dist/full/index.mjs +3 -2
- package/dist/index.js +7 -6
- package/dist/index.mjs +3 -2
- package/dist/metadata/index.js +1 -1
- package/dist/metadata/index.mjs +1 -1
- package/dist/runtime/index.js +1 -0
- package/dist/runtime/index.mjs +1 -0
- package/dist/static/index.js +1 -0
- package/dist/static/index.mjs +1 -0
- package/package.json +1 -1
- package/dist/chunk-GW5YOUB7.js +0 -1054
- package/dist/chunk-UYVGHUC5.mjs +0 -1027
package/dist/chunk-GW5YOUB7.js
DELETED
|
@@ -1,1054 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
var fs = require('fs/promises');
|
|
4
|
-
var fsSync = require('fs');
|
|
5
|
-
var path = require('path');
|
|
6
|
-
var url = require('url');
|
|
7
|
-
var compilerSfc = require('@vue/compiler-sfc');
|
|
8
|
-
var ts = require('typescript');
|
|
9
|
-
|
|
10
|
-
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
11
|
-
function _interopNamespace(e) {
|
|
12
|
-
if (e && e.__esModule) return e;
|
|
13
|
-
var n = Object.create(null);
|
|
14
|
-
if (e) {
|
|
15
|
-
Object.keys(e).forEach(function (k) {
|
|
16
|
-
if (k !== 'default') {
|
|
17
|
-
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
18
|
-
Object.defineProperty(n, k, d.get ? d : {
|
|
19
|
-
enumerable: true,
|
|
20
|
-
get: function () { return e[k]; }
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
n.default = e;
|
|
26
|
-
return Object.freeze(n);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
var fs__namespace = /*#__PURE__*/_interopNamespace(fs);
|
|
30
|
-
var fsSync__namespace = /*#__PURE__*/_interopNamespace(fsSync);
|
|
31
|
-
var path__namespace = /*#__PURE__*/_interopNamespace(path);
|
|
32
|
-
var ts__namespace = /*#__PURE__*/_interopNamespace(ts);
|
|
33
|
-
|
|
34
|
-
// src/analysis/componentRegistry.ts
|
|
35
|
-
var __filename$1 = url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('chunk-GW5YOUB7.js', document.baseURI).href)));
|
|
36
|
-
var __dirname$1 = path__namespace.dirname(__filename$1);
|
|
37
|
-
function resolveComponentsPath() {
|
|
38
|
-
const prodPath = path__namespace.join(__dirname$1, "../components");
|
|
39
|
-
if (fsSync__namespace.existsSync(prodPath)) return prodPath;
|
|
40
|
-
const devPath = path__namespace.join(__dirname$1, "../../components");
|
|
41
|
-
if (fsSync__namespace.existsSync(devPath)) return devPath;
|
|
42
|
-
return prodPath;
|
|
43
|
-
}
|
|
44
|
-
var ComponentRegistry = class {
|
|
45
|
-
constructor() {
|
|
46
|
-
this.components = /* @__PURE__ */ new Map();
|
|
47
|
-
this.loaded = false;
|
|
48
|
-
this.watcher = null;
|
|
49
|
-
// Path to components directory (resolved dynamically for dev/prod compatibility)
|
|
50
|
-
this.metadataPath = resolveComponentsPath();
|
|
51
|
-
}
|
|
52
|
-
async load() {
|
|
53
|
-
if (this.loaded) return;
|
|
54
|
-
await this.reload();
|
|
55
|
-
this.loaded = true;
|
|
56
|
-
this.startWatching();
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* 重新加载所有组件元数据
|
|
60
|
-
*/
|
|
61
|
-
async reload() {
|
|
62
|
-
const newComponents = /* @__PURE__ */ new Map();
|
|
63
|
-
try {
|
|
64
|
-
const files = await fs__namespace.readdir(this.metadataPath);
|
|
65
|
-
for (const file of files) {
|
|
66
|
-
if (!file.endsWith(".json")) continue;
|
|
67
|
-
const content = await fs__namespace.readFile(path__namespace.join(this.metadataPath, file), "utf-8");
|
|
68
|
-
const data = JSON.parse(content);
|
|
69
|
-
newComponents.set(data.name, data);
|
|
70
|
-
if (data.subComponents) {
|
|
71
|
-
for (const sub of data.subComponents) {
|
|
72
|
-
newComponents.set(sub.name, sub);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
this.components = newComponents;
|
|
77
|
-
console.log(`[ComponentRegistry] ${this.loaded ? "Reloaded" : "Loaded"} ${this.components.size} components.`);
|
|
78
|
-
} catch (error) {
|
|
79
|
-
console.error("[ComponentRegistry] Failed to load metadata:", error);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* 开始监听文件变化
|
|
84
|
-
*/
|
|
85
|
-
startWatching() {
|
|
86
|
-
if (this.watcher) return;
|
|
87
|
-
try {
|
|
88
|
-
let reloadTimeout = null;
|
|
89
|
-
this.watcher = fsSync__namespace.watch(this.metadataPath, (eventType, filename) => {
|
|
90
|
-
if (!filename?.endsWith(".json")) return;
|
|
91
|
-
console.log(`[ComponentRegistry] Detected change: ${filename} (${eventType})`);
|
|
92
|
-
if (reloadTimeout) {
|
|
93
|
-
clearTimeout(reloadTimeout);
|
|
94
|
-
}
|
|
95
|
-
reloadTimeout = setTimeout(() => {
|
|
96
|
-
this.reload();
|
|
97
|
-
}, 500);
|
|
98
|
-
});
|
|
99
|
-
console.log(`[ComponentRegistry] Hot reload enabled for: ${this.metadataPath}`);
|
|
100
|
-
} catch (error) {
|
|
101
|
-
console.warn("[ComponentRegistry] Could not enable hot reload:", error);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
/**
|
|
105
|
-
* 停止监听
|
|
106
|
-
*/
|
|
107
|
-
stopWatching() {
|
|
108
|
-
if (this.watcher) {
|
|
109
|
-
this.watcher.close();
|
|
110
|
-
this.watcher = null;
|
|
111
|
-
console.log("[ComponentRegistry] Hot reload disabled.");
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
getComponent(name) {
|
|
115
|
-
return this.components.get(name);
|
|
116
|
-
}
|
|
117
|
-
getAllComponentNames() {
|
|
118
|
-
return Array.from(this.components.keys());
|
|
119
|
-
}
|
|
120
|
-
isKnownComponent(name) {
|
|
121
|
-
return this.components.has(name);
|
|
122
|
-
}
|
|
123
|
-
};
|
|
124
|
-
var componentRegistry = new ComponentRegistry();
|
|
125
|
-
var __filename2 = url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('chunk-GW5YOUB7.js', document.baseURI).href)));
|
|
126
|
-
var __dirname2 = path__namespace.dirname(__filename2);
|
|
127
|
-
function resolveHooksPath() {
|
|
128
|
-
const prodPath = path__namespace.join(__dirname2, "../hooks");
|
|
129
|
-
if (fsSync__namespace.existsSync(prodPath)) return prodPath;
|
|
130
|
-
const devPath = path__namespace.join(__dirname2, "../../hooks");
|
|
131
|
-
if (fsSync__namespace.existsSync(devPath)) return devPath;
|
|
132
|
-
return prodPath;
|
|
133
|
-
}
|
|
134
|
-
var HooksRegistry = class {
|
|
135
|
-
constructor() {
|
|
136
|
-
this.hooks = /* @__PURE__ */ new Map();
|
|
137
|
-
this.loaded = false;
|
|
138
|
-
this.watcher = null;
|
|
139
|
-
this.metadataPath = resolveHooksPath();
|
|
140
|
-
}
|
|
141
|
-
async load() {
|
|
142
|
-
if (this.loaded) return;
|
|
143
|
-
await this.reload();
|
|
144
|
-
this.loaded = true;
|
|
145
|
-
this.startWatching();
|
|
146
|
-
}
|
|
147
|
-
/**
|
|
148
|
-
* 重新加载所有 hooks 元数据
|
|
149
|
-
*/
|
|
150
|
-
async reload() {
|
|
151
|
-
const newHooks = /* @__PURE__ */ new Map();
|
|
152
|
-
try {
|
|
153
|
-
const files = await fs__namespace.readdir(this.metadataPath);
|
|
154
|
-
for (const file of files) {
|
|
155
|
-
if (!file.endsWith(".json")) continue;
|
|
156
|
-
const content = await fs__namespace.readFile(path__namespace.join(this.metadataPath, file), "utf-8");
|
|
157
|
-
const data = JSON.parse(content);
|
|
158
|
-
newHooks.set(data.name, data);
|
|
159
|
-
}
|
|
160
|
-
this.hooks = newHooks;
|
|
161
|
-
if (this.hooks.size > 0) {
|
|
162
|
-
console.log(`[HooksRegistry] ${this.loaded ? "Reloaded" : "Loaded"} ${this.hooks.size} hooks.`);
|
|
163
|
-
}
|
|
164
|
-
} catch (error) {
|
|
165
|
-
if (error.code !== "ENOENT") {
|
|
166
|
-
console.error("[HooksRegistry] Failed to load metadata:", error);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
/**
|
|
171
|
-
* 开始监听文件变化
|
|
172
|
-
*/
|
|
173
|
-
startWatching() {
|
|
174
|
-
if (this.watcher) return;
|
|
175
|
-
try {
|
|
176
|
-
let reloadTimeout = null;
|
|
177
|
-
this.watcher = fsSync__namespace.watch(this.metadataPath, (eventType, filename) => {
|
|
178
|
-
if (!filename?.endsWith(".json")) return;
|
|
179
|
-
console.log(`[HooksRegistry] Detected change: ${filename} (${eventType})`);
|
|
180
|
-
if (reloadTimeout) {
|
|
181
|
-
clearTimeout(reloadTimeout);
|
|
182
|
-
}
|
|
183
|
-
reloadTimeout = setTimeout(() => {
|
|
184
|
-
this.reload();
|
|
185
|
-
}, 500);
|
|
186
|
-
});
|
|
187
|
-
} catch (error) {
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
/**
|
|
191
|
-
* 停止监听
|
|
192
|
-
*/
|
|
193
|
-
stopWatching() {
|
|
194
|
-
if (this.watcher) {
|
|
195
|
-
this.watcher.close();
|
|
196
|
-
this.watcher = null;
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
getHook(name) {
|
|
200
|
-
return this.hooks.get(name);
|
|
201
|
-
}
|
|
202
|
-
getAllHookNames() {
|
|
203
|
-
return Array.from(this.hooks.keys());
|
|
204
|
-
}
|
|
205
|
-
isKnownHook(name) {
|
|
206
|
-
return this.hooks.has(name);
|
|
207
|
-
}
|
|
208
|
-
/**
|
|
209
|
-
* 获取 hook 的必填参数数量
|
|
210
|
-
*/
|
|
211
|
-
getRequiredParamCount(name) {
|
|
212
|
-
const hook = this.hooks.get(name);
|
|
213
|
-
if (!hook) return 0;
|
|
214
|
-
return hook.params.filter((p) => p.required).length;
|
|
215
|
-
}
|
|
216
|
-
/**
|
|
217
|
-
* 获取 hook 的总参数数量
|
|
218
|
-
*/
|
|
219
|
-
getTotalParamCount(name) {
|
|
220
|
-
const hook = this.hooks.get(name);
|
|
221
|
-
if (!hook) return 0;
|
|
222
|
-
return hook.params.length;
|
|
223
|
-
}
|
|
224
|
-
};
|
|
225
|
-
var hooksRegistry = new HooksRegistry();
|
|
226
|
-
|
|
227
|
-
// src/analysis/astReviewer.ts
|
|
228
|
-
var VUE_BUILTINS = /* @__PURE__ */ new Set([
|
|
229
|
-
// Reactivity
|
|
230
|
-
"ref",
|
|
231
|
-
"reactive",
|
|
232
|
-
"computed",
|
|
233
|
-
"watch",
|
|
234
|
-
"watchEffect",
|
|
235
|
-
"watchPostEffect",
|
|
236
|
-
"watchSyncEffect",
|
|
237
|
-
"toRef",
|
|
238
|
-
"toRefs",
|
|
239
|
-
"toRaw",
|
|
240
|
-
"markRaw",
|
|
241
|
-
"isRef",
|
|
242
|
-
"isReactive",
|
|
243
|
-
"isReadonly",
|
|
244
|
-
"isProxy",
|
|
245
|
-
"shallowRef",
|
|
246
|
-
"shallowReactive",
|
|
247
|
-
"shallowReadonly",
|
|
248
|
-
"triggerRef",
|
|
249
|
-
"customRef",
|
|
250
|
-
"readonly",
|
|
251
|
-
"effectScope",
|
|
252
|
-
"getCurrentScope",
|
|
253
|
-
"onScopeDispose",
|
|
254
|
-
// Lifecycle
|
|
255
|
-
"onMounted",
|
|
256
|
-
"onUnmounted",
|
|
257
|
-
"onBeforeMount",
|
|
258
|
-
"onBeforeUnmount",
|
|
259
|
-
"onUpdated",
|
|
260
|
-
"onBeforeUpdate",
|
|
261
|
-
"onActivated",
|
|
262
|
-
"onDeactivated",
|
|
263
|
-
"onErrorCaptured",
|
|
264
|
-
"onRenderTracked",
|
|
265
|
-
"onRenderTriggered",
|
|
266
|
-
"onServerPrefetch",
|
|
267
|
-
// Composition API
|
|
268
|
-
"defineProps",
|
|
269
|
-
"defineEmits",
|
|
270
|
-
"defineExpose",
|
|
271
|
-
"defineOptions",
|
|
272
|
-
"defineSlots",
|
|
273
|
-
"defineModel",
|
|
274
|
-
"withDefaults",
|
|
275
|
-
"useSlots",
|
|
276
|
-
"useAttrs",
|
|
277
|
-
// Others
|
|
278
|
-
"nextTick",
|
|
279
|
-
"inject",
|
|
280
|
-
"provide",
|
|
281
|
-
"getCurrentInstance",
|
|
282
|
-
"h",
|
|
283
|
-
"createVNode",
|
|
284
|
-
"resolveComponent",
|
|
285
|
-
"resolveDirective",
|
|
286
|
-
"withDirectives"
|
|
287
|
-
]);
|
|
288
|
-
var JS_GLOBALS = /* @__PURE__ */ new Set([
|
|
289
|
-
"console",
|
|
290
|
-
"window",
|
|
291
|
-
"document",
|
|
292
|
-
"globalThis",
|
|
293
|
-
"self",
|
|
294
|
-
"Math",
|
|
295
|
-
"JSON",
|
|
296
|
-
"Date",
|
|
297
|
-
"Array",
|
|
298
|
-
"Object",
|
|
299
|
-
"String",
|
|
300
|
-
"Number",
|
|
301
|
-
"Boolean",
|
|
302
|
-
"Promise",
|
|
303
|
-
"Error",
|
|
304
|
-
"TypeError",
|
|
305
|
-
"RangeError",
|
|
306
|
-
"SyntaxError",
|
|
307
|
-
"RegExp",
|
|
308
|
-
"Map",
|
|
309
|
-
"Set",
|
|
310
|
-
"WeakMap",
|
|
311
|
-
"WeakSet",
|
|
312
|
-
"Symbol",
|
|
313
|
-
"Proxy",
|
|
314
|
-
"Reflect",
|
|
315
|
-
"parseInt",
|
|
316
|
-
"parseFloat",
|
|
317
|
-
"isNaN",
|
|
318
|
-
"isFinite",
|
|
319
|
-
"encodeURI",
|
|
320
|
-
"decodeURI",
|
|
321
|
-
"encodeURIComponent",
|
|
322
|
-
"decodeURIComponent",
|
|
323
|
-
"atob",
|
|
324
|
-
"btoa",
|
|
325
|
-
"setTimeout",
|
|
326
|
-
"setInterval",
|
|
327
|
-
"clearTimeout",
|
|
328
|
-
"clearInterval",
|
|
329
|
-
"requestAnimationFrame",
|
|
330
|
-
"cancelAnimationFrame",
|
|
331
|
-
"fetch",
|
|
332
|
-
"URL",
|
|
333
|
-
"URLSearchParams",
|
|
334
|
-
"FormData",
|
|
335
|
-
"Blob",
|
|
336
|
-
"File",
|
|
337
|
-
"FileReader",
|
|
338
|
-
"undefined",
|
|
339
|
-
"null",
|
|
340
|
-
"NaN",
|
|
341
|
-
"Infinity",
|
|
342
|
-
"true",
|
|
343
|
-
"false"
|
|
344
|
-
]);
|
|
345
|
-
var JS_KEYWORDS = /* @__PURE__ */ new Set([
|
|
346
|
-
"if",
|
|
347
|
-
"else",
|
|
348
|
-
"for",
|
|
349
|
-
"while",
|
|
350
|
-
"do",
|
|
351
|
-
"switch",
|
|
352
|
-
"case",
|
|
353
|
-
"break",
|
|
354
|
-
"continue",
|
|
355
|
-
"return",
|
|
356
|
-
"try",
|
|
357
|
-
"catch",
|
|
358
|
-
"finally",
|
|
359
|
-
"throw",
|
|
360
|
-
"new",
|
|
361
|
-
"delete",
|
|
362
|
-
"typeof",
|
|
363
|
-
"instanceof",
|
|
364
|
-
"void",
|
|
365
|
-
"in",
|
|
366
|
-
"of",
|
|
367
|
-
"this",
|
|
368
|
-
"class",
|
|
369
|
-
"extends",
|
|
370
|
-
"super",
|
|
371
|
-
"import",
|
|
372
|
-
"export",
|
|
373
|
-
"default",
|
|
374
|
-
"const",
|
|
375
|
-
"let",
|
|
376
|
-
"var",
|
|
377
|
-
"function",
|
|
378
|
-
"async",
|
|
379
|
-
"await",
|
|
380
|
-
"yield",
|
|
381
|
-
"true",
|
|
382
|
-
"false",
|
|
383
|
-
"null",
|
|
384
|
-
"undefined",
|
|
385
|
-
"NaN",
|
|
386
|
-
"Infinity"
|
|
387
|
-
]);
|
|
388
|
-
var TEMPLATE_BUILTINS = /* @__PURE__ */ new Set([
|
|
389
|
-
"$event",
|
|
390
|
-
"$refs",
|
|
391
|
-
"$slots",
|
|
392
|
-
"$attrs",
|
|
393
|
-
"$emit",
|
|
394
|
-
"$el",
|
|
395
|
-
"$parent",
|
|
396
|
-
"$root",
|
|
397
|
-
"$data",
|
|
398
|
-
"$options",
|
|
399
|
-
"$route",
|
|
400
|
-
"$router",
|
|
401
|
-
"$t",
|
|
402
|
-
"$i18n",
|
|
403
|
-
"$n",
|
|
404
|
-
"$d",
|
|
405
|
-
"$te"
|
|
406
|
-
// Vue Router & i18n
|
|
407
|
-
]);
|
|
408
|
-
var NATIVE_EVENTS = /* @__PURE__ */ new Set([
|
|
409
|
-
// Mouse
|
|
410
|
-
"click",
|
|
411
|
-
"dblclick",
|
|
412
|
-
"mousedown",
|
|
413
|
-
"mouseup",
|
|
414
|
-
"mousemove",
|
|
415
|
-
"mouseenter",
|
|
416
|
-
"mouseleave",
|
|
417
|
-
"mouseover",
|
|
418
|
-
"mouseout",
|
|
419
|
-
// Keyboard
|
|
420
|
-
"keydown",
|
|
421
|
-
"keyup",
|
|
422
|
-
"keypress",
|
|
423
|
-
// Focus
|
|
424
|
-
"focus",
|
|
425
|
-
"blur",
|
|
426
|
-
"focusin",
|
|
427
|
-
"focusout",
|
|
428
|
-
// Form
|
|
429
|
-
"input",
|
|
430
|
-
"change",
|
|
431
|
-
"submit",
|
|
432
|
-
"reset",
|
|
433
|
-
"invalid",
|
|
434
|
-
// Drag
|
|
435
|
-
"drag",
|
|
436
|
-
"dragstart",
|
|
437
|
-
"dragend",
|
|
438
|
-
"dragenter",
|
|
439
|
-
"dragleave",
|
|
440
|
-
"dragover",
|
|
441
|
-
"drop",
|
|
442
|
-
// Clipboard
|
|
443
|
-
"copy",
|
|
444
|
-
"cut",
|
|
445
|
-
"paste",
|
|
446
|
-
// Touch
|
|
447
|
-
"touchstart",
|
|
448
|
-
"touchend",
|
|
449
|
-
"touchmove",
|
|
450
|
-
"touchcancel",
|
|
451
|
-
// Scroll & Resize
|
|
452
|
-
"scroll",
|
|
453
|
-
"resize",
|
|
454
|
-
"wheel",
|
|
455
|
-
// Other
|
|
456
|
-
"contextmenu",
|
|
457
|
-
"pointerdown",
|
|
458
|
-
"pointerup",
|
|
459
|
-
"pointermove",
|
|
460
|
-
"pointerenter",
|
|
461
|
-
"pointerleave"
|
|
462
|
-
]);
|
|
463
|
-
var NATIVE_HTML_ATTRIBUTES = /* @__PURE__ */ new Set([
|
|
464
|
-
// Form attributes
|
|
465
|
-
"maxlength",
|
|
466
|
-
"minlength",
|
|
467
|
-
"max",
|
|
468
|
-
"min",
|
|
469
|
-
"step",
|
|
470
|
-
"pattern",
|
|
471
|
-
"autocomplete",
|
|
472
|
-
"autofocus",
|
|
473
|
-
"required",
|
|
474
|
-
"readonly",
|
|
475
|
-
"disabled",
|
|
476
|
-
"checked",
|
|
477
|
-
"selected",
|
|
478
|
-
"multiple",
|
|
479
|
-
"accept",
|
|
480
|
-
"name",
|
|
481
|
-
"value",
|
|
482
|
-
"placeholder",
|
|
483
|
-
"form",
|
|
484
|
-
"formaction",
|
|
485
|
-
"formmethod",
|
|
486
|
-
"formtarget",
|
|
487
|
-
// Link/Navigation attributes
|
|
488
|
-
"href",
|
|
489
|
-
"target",
|
|
490
|
-
"rel",
|
|
491
|
-
"download",
|
|
492
|
-
"hreflang",
|
|
493
|
-
"ping",
|
|
494
|
-
"referrerpolicy",
|
|
495
|
-
// Media attributes
|
|
496
|
-
"src",
|
|
497
|
-
"alt",
|
|
498
|
-
"width",
|
|
499
|
-
"height",
|
|
500
|
-
"loading",
|
|
501
|
-
"decoding",
|
|
502
|
-
"crossorigin",
|
|
503
|
-
"controls",
|
|
504
|
-
"autoplay",
|
|
505
|
-
"loop",
|
|
506
|
-
"muted",
|
|
507
|
-
"poster",
|
|
508
|
-
"preload",
|
|
509
|
-
// Table attributes
|
|
510
|
-
"colspan",
|
|
511
|
-
"rowspan",
|
|
512
|
-
"headers",
|
|
513
|
-
"scope",
|
|
514
|
-
// Global attributes
|
|
515
|
-
"id",
|
|
516
|
-
"title",
|
|
517
|
-
"lang",
|
|
518
|
-
"dir",
|
|
519
|
-
"hidden",
|
|
520
|
-
"tabindex",
|
|
521
|
-
"accesskey",
|
|
522
|
-
"draggable",
|
|
523
|
-
"contenteditable",
|
|
524
|
-
"spellcheck",
|
|
525
|
-
"translate",
|
|
526
|
-
"role",
|
|
527
|
-
// ARIA attributes (common ones)
|
|
528
|
-
"aria-label",
|
|
529
|
-
"aria-labelledby",
|
|
530
|
-
"aria-describedby",
|
|
531
|
-
"aria-hidden",
|
|
532
|
-
"aria-expanded",
|
|
533
|
-
"aria-selected",
|
|
534
|
-
"aria-checked",
|
|
535
|
-
"aria-disabled",
|
|
536
|
-
"aria-controls",
|
|
537
|
-
"aria-haspopup"
|
|
538
|
-
]);
|
|
539
|
-
var VERSATILE_COMPONENTS = /* @__PURE__ */ new Set([
|
|
540
|
-
"BillTypes",
|
|
541
|
-
"Region",
|
|
542
|
-
"AZ",
|
|
543
|
-
"LayoutContent",
|
|
544
|
-
"CardContent",
|
|
545
|
-
"ProTable",
|
|
546
|
-
"PaginationPlus",
|
|
547
|
-
"Status",
|
|
548
|
-
"TableColumnId",
|
|
549
|
-
"IconTooltip",
|
|
550
|
-
"ButtonLink"
|
|
551
|
-
]);
|
|
552
|
-
var DUAL_EXPORT_COMPONENTS = /* @__PURE__ */ new Set([
|
|
553
|
-
"TableColumn"
|
|
554
|
-
// 可与 ProTable 配合从 versatile 导入,也可与 Table 配合从 king-design 导入
|
|
555
|
-
]);
|
|
556
|
-
var NESTING_RULES = {
|
|
557
|
-
"DropdownItem": ["DropdownMenu"],
|
|
558
|
-
"DropdownMenu": ["Dropdown"],
|
|
559
|
-
"TableColumn": ["Table", "ProTable"],
|
|
560
|
-
"FormItem": ["Form"],
|
|
561
|
-
"Tab": ["Tabs"],
|
|
562
|
-
"Step": ["Steps"],
|
|
563
|
-
"MenuItem": ["Menu", "SubMenu"],
|
|
564
|
-
"SubMenu": ["Menu"],
|
|
565
|
-
"BreadcrumbItem": ["Breadcrumb"],
|
|
566
|
-
"CollapseItem": ["Collapse"],
|
|
567
|
-
"CarouselItem": ["Carousel"],
|
|
568
|
-
"DescriptionItem": ["Descriptions"]
|
|
569
|
-
};
|
|
570
|
-
async function analyzeCodeWithAST(code) {
|
|
571
|
-
await componentRegistry.load();
|
|
572
|
-
await hooksRegistry.load();
|
|
573
|
-
const violations = [];
|
|
574
|
-
const { descriptor, errors } = compilerSfc.parse(code);
|
|
575
|
-
if (errors.length > 0) {
|
|
576
|
-
return violations;
|
|
577
|
-
}
|
|
578
|
-
const scriptContent = descriptor.scriptSetup?.content || descriptor.script?.content || "";
|
|
579
|
-
if (scriptContent) {
|
|
580
|
-
const sourceFile = ts__namespace.createSourceFile(
|
|
581
|
-
"temp.ts",
|
|
582
|
-
scriptContent,
|
|
583
|
-
ts__namespace.ScriptTarget.Latest,
|
|
584
|
-
true
|
|
585
|
-
);
|
|
586
|
-
ts__namespace.forEachChild(sourceFile, (node) => {
|
|
587
|
-
if (ts__namespace.isImportDeclaration(node)) {
|
|
588
|
-
checkImport(node, violations, sourceFile);
|
|
589
|
-
}
|
|
590
|
-
});
|
|
591
|
-
}
|
|
592
|
-
if (descriptor.template?.ast) {
|
|
593
|
-
checkTemplate(descriptor.template.ast, violations, []);
|
|
594
|
-
}
|
|
595
|
-
if (scriptContent && descriptor.template) {
|
|
596
|
-
const scriptBindings = extractScriptBindings(scriptContent);
|
|
597
|
-
checkTemplateVariables(descriptor.template, scriptBindings, violations);
|
|
598
|
-
}
|
|
599
|
-
if (scriptContent) {
|
|
600
|
-
const scriptBindings = extractScriptBindings(scriptContent);
|
|
601
|
-
checkScriptFunctionCalls(scriptContent, scriptBindings, violations);
|
|
602
|
-
}
|
|
603
|
-
return violations;
|
|
604
|
-
}
|
|
605
|
-
function extractScriptBindings(scriptContent) {
|
|
606
|
-
const bindings = /* @__PURE__ */ new Set();
|
|
607
|
-
VUE_BUILTINS.forEach((b) => bindings.add(b));
|
|
608
|
-
JS_GLOBALS.forEach((b) => bindings.add(b));
|
|
609
|
-
try {
|
|
610
|
-
let extractNames2 = function(name) {
|
|
611
|
-
if (ts__namespace.isIdentifier(name)) {
|
|
612
|
-
bindings.add(name.text);
|
|
613
|
-
} else if (ts__namespace.isObjectBindingPattern(name) || ts__namespace.isArrayBindingPattern(name)) {
|
|
614
|
-
name.elements.forEach((element) => {
|
|
615
|
-
if (ts__namespace.isBindingElement(element)) {
|
|
616
|
-
extractNames2(element.name);
|
|
617
|
-
}
|
|
618
|
-
});
|
|
619
|
-
}
|
|
620
|
-
}, visit2 = function(node) {
|
|
621
|
-
if (ts__namespace.isVariableStatement(node)) {
|
|
622
|
-
node.declarationList.declarations.forEach((decl) => extractNames2(decl.name));
|
|
623
|
-
} else if (ts__namespace.isFunctionDeclaration(node) && node.name) {
|
|
624
|
-
bindings.add(node.name.text);
|
|
625
|
-
} else if (ts__namespace.isClassDeclaration(node) && node.name) {
|
|
626
|
-
bindings.add(node.name.text);
|
|
627
|
-
} else if (ts__namespace.isImportDeclaration(node)) {
|
|
628
|
-
const namedBindings = node.importClause?.namedBindings;
|
|
629
|
-
if (namedBindings && ts__namespace.isNamedImports(namedBindings)) {
|
|
630
|
-
namedBindings.elements.forEach((element) => {
|
|
631
|
-
bindings.add(element.name.text);
|
|
632
|
-
});
|
|
633
|
-
}
|
|
634
|
-
if (node.importClause?.name) {
|
|
635
|
-
bindings.add(node.importClause.name.text);
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
};
|
|
639
|
-
var extractNames = extractNames2, visit = visit2;
|
|
640
|
-
const sourceFile = ts__namespace.createSourceFile(
|
|
641
|
-
"temp.ts",
|
|
642
|
-
scriptContent,
|
|
643
|
-
ts__namespace.ScriptTarget.Latest,
|
|
644
|
-
true
|
|
645
|
-
);
|
|
646
|
-
sourceFile.statements.forEach(visit2);
|
|
647
|
-
} catch (err) {
|
|
648
|
-
return bindings;
|
|
649
|
-
}
|
|
650
|
-
return bindings;
|
|
651
|
-
}
|
|
652
|
-
function checkScriptFunctionCalls(scriptContent, bindings, violations) {
|
|
653
|
-
try {
|
|
654
|
-
let visit2 = function(node) {
|
|
655
|
-
if (ts__namespace.isCallExpression(node)) {
|
|
656
|
-
const callee = node.expression;
|
|
657
|
-
if (ts__namespace.isIdentifier(callee)) {
|
|
658
|
-
const funcName = callee.text;
|
|
659
|
-
const isHookCall = funcName.startsWith("use") && funcName.length > 3 && funcName[3] === funcName[3].toUpperCase();
|
|
660
|
-
if (isHookCall) {
|
|
661
|
-
if (!bindings.has(funcName)) {
|
|
662
|
-
violations.push({
|
|
663
|
-
rule: "\u4F7F\u7528\u4E86\u672A\u5BFC\u5165\u7684 Hook",
|
|
664
|
-
match: `${funcName}()`,
|
|
665
|
-
suggestion: `\u8BF7\u5148\u5BFC\u5165 ${funcName}: import { ${funcName} } from '@ksyun-internal/versatile'`
|
|
666
|
-
});
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
ts__namespace.forEachChild(node, visit2);
|
|
672
|
-
};
|
|
673
|
-
var visit = visit2;
|
|
674
|
-
const sourceFile = ts__namespace.createSourceFile(
|
|
675
|
-
"temp.ts",
|
|
676
|
-
scriptContent,
|
|
677
|
-
ts__namespace.ScriptTarget.Latest,
|
|
678
|
-
true
|
|
679
|
-
);
|
|
680
|
-
ts__namespace.forEachChild(sourceFile, visit2);
|
|
681
|
-
} catch (err) {
|
|
682
|
-
}
|
|
683
|
-
}
|
|
684
|
-
function checkTemplateVariables(template, bindings, violations) {
|
|
685
|
-
const templateContent = template.content;
|
|
686
|
-
const templateAst = template.ast;
|
|
687
|
-
const localVariables = /* @__PURE__ */ new Set();
|
|
688
|
-
if (templateAst) {
|
|
689
|
-
let walk2 = function(node) {
|
|
690
|
-
if (node.type === 0 || node.type === 1) {
|
|
691
|
-
const vFor = node.props?.find((p) => p.type === 7 && p.name === "for");
|
|
692
|
-
if (vFor && vFor.exp) {
|
|
693
|
-
if (vFor.parseResult) {
|
|
694
|
-
const { value, key, index } = vFor.parseResult;
|
|
695
|
-
if (value) extractIdentifiersFromTemplateAST(value, localVariables);
|
|
696
|
-
if (key) extractIdentifiersFromTemplateAST(key, localVariables);
|
|
697
|
-
if (index) extractIdentifiersFromTemplateAST(index, localVariables);
|
|
698
|
-
} else if (vFor.exp.content) {
|
|
699
|
-
extractIdentifiersFromTemplateAST(vFor.exp, localVariables);
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
|
-
const vSlot = node.props?.find((p) => p.type === 7 && p.name === "slot");
|
|
703
|
-
if (vSlot && vSlot.exp) {
|
|
704
|
-
extractIdentifiersFromTemplateAST(vSlot.exp, localVariables);
|
|
705
|
-
}
|
|
706
|
-
if (node.children) {
|
|
707
|
-
node.children.forEach(walk2);
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
};
|
|
711
|
-
walk2(templateAst);
|
|
712
|
-
}
|
|
713
|
-
const allBindings = /* @__PURE__ */ new Set([...bindings, ...localVariables]);
|
|
714
|
-
const interpolationRegex = /\{\{\s*([^}]+)\s*\}\}/g;
|
|
715
|
-
let match;
|
|
716
|
-
while ((match = interpolationRegex.exec(templateContent)) !== null) {
|
|
717
|
-
const expression = match[1].trim();
|
|
718
|
-
const { identifiers, locals } = extractIdentifiersFromExpression(expression);
|
|
719
|
-
const combinedBindings = /* @__PURE__ */ new Set([...allBindings, ...locals]);
|
|
720
|
-
for (const id of identifiers) {
|
|
721
|
-
if (!combinedBindings.has(id) && !isTemplateBuiltin(id)) {
|
|
722
|
-
violations.push({
|
|
723
|
-
rule: `\u6A21\u677F\u5F15\u7528\u4E86\u672A\u5B9A\u4E49\u7684\u53D8\u91CF: ${id}`,
|
|
724
|
-
match: `{{ ${expression} }}`,
|
|
725
|
-
suggestion: `\u8BF7\u786E\u4FDD\u5728 <script setup> \u4E2D\u5B9A\u4E49\u53D8\u91CF ${id}\uFF0C\u6216\u68C0\u67E5\u62FC\u5199\u662F\u5426\u6B63\u786E`
|
|
726
|
-
});
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
|
-
const bindRegex = /(?::|v-bind:)[\w.-]+="([^"]+)"/g;
|
|
731
|
-
while ((match = bindRegex.exec(templateContent)) !== null) {
|
|
732
|
-
const expression = match[1].trim();
|
|
733
|
-
const { identifiers, locals } = extractIdentifiersFromExpression(expression);
|
|
734
|
-
const combinedBindings = /* @__PURE__ */ new Set([...allBindings, ...locals]);
|
|
735
|
-
for (const id of identifiers) {
|
|
736
|
-
if (!combinedBindings.has(id) && !isTemplateBuiltin(id)) {
|
|
737
|
-
violations.push({
|
|
738
|
-
rule: `\u7ED1\u5B9A\u5C5E\u6027\u5F15\u7528\u4E86\u672A\u5B9A\u4E49\u7684\u53D8\u91CF: ${id}`,
|
|
739
|
-
match: match[0],
|
|
740
|
-
suggestion: `\u8BF7\u786E\u4FDD\u5728 <script setup> \u4E2D\u5B9A\u4E49\u53D8\u91CF ${id}`
|
|
741
|
-
});
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
|
-
const eventRegex = /(?:@|v-on:)[\w.-]+="([^"]+)"/g;
|
|
746
|
-
while ((match = eventRegex.exec(templateContent)) !== null) {
|
|
747
|
-
const expression = match[1].trim();
|
|
748
|
-
const { identifiers, locals } = extractIdentifiersFromExpression(expression);
|
|
749
|
-
const combinedBindings = /* @__PURE__ */ new Set([...allBindings, ...locals]);
|
|
750
|
-
for (const id of identifiers) {
|
|
751
|
-
if (!combinedBindings.has(id) && !isTemplateBuiltin(id)) {
|
|
752
|
-
violations.push({
|
|
753
|
-
rule: `\u4E8B\u4EF6\u5904\u7406\u5668\u5F15\u7528\u4E86\u672A\u5B9A\u4E49\u7684\u51FD\u6570/\u53D8\u91CF: ${id}`,
|
|
754
|
-
match: match[0],
|
|
755
|
-
suggestion: `\u8BF7\u786E\u4FDD\u5728 <script setup> \u4E2D\u5B9A\u4E49 ${id}`
|
|
756
|
-
});
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
}
|
|
760
|
-
}
|
|
761
|
-
function extractIdentifiersFromTemplateAST(node, set) {
|
|
762
|
-
if (!node || typeof node !== "object") return;
|
|
763
|
-
const n = node;
|
|
764
|
-
if (n.type === 4 && n.content) {
|
|
765
|
-
const matches = n.content.match(/\b([a-zA-Z_$][a-zA-Z0-9_$]*)\b/g);
|
|
766
|
-
if (matches) {
|
|
767
|
-
matches.forEach((m) => {
|
|
768
|
-
if (!isJsKeyword(m)) set.add(m);
|
|
769
|
-
});
|
|
770
|
-
}
|
|
771
|
-
} else if (n.type === 8 && n.children) {
|
|
772
|
-
n.children.forEach((c) => extractIdentifiersFromTemplateAST(c, set));
|
|
773
|
-
}
|
|
774
|
-
}
|
|
775
|
-
function extractIdentifiersFromExpression(expression) {
|
|
776
|
-
const identifiers = [];
|
|
777
|
-
const locals = [];
|
|
778
|
-
const arrowFuncRegex = /(?:(?:\(([^)]+)\))|([a-zA-Z_$][\w$]*))\s*=>/g;
|
|
779
|
-
let arrowMatch;
|
|
780
|
-
while ((arrowMatch = arrowFuncRegex.exec(expression)) !== null) {
|
|
781
|
-
const params = arrowMatch[1] || arrowMatch[2];
|
|
782
|
-
if (params) {
|
|
783
|
-
const paramNames = params.split(",").map((p) => p.trim().split(":")[0].trim());
|
|
784
|
-
paramNames.forEach((p) => {
|
|
785
|
-
const subMatches = p.match(/\b([a-zA-Z_$][\w$]*)\b/g);
|
|
786
|
-
if (subMatches) subMatches.forEach((sm) => {
|
|
787
|
-
if (!isJsKeyword(sm)) locals.push(sm);
|
|
788
|
-
});
|
|
789
|
-
});
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
const cleanExpr = expression.replace(/'[^']*'/g, "").replace(/"[^"]*"/g, "").replace(/`[^`]*`/g, "");
|
|
793
|
-
const idRegex = /(?:^|[^.\w$])([a-zA-Z_$][\w$]*)\b(?!\s*:)/g;
|
|
794
|
-
let match;
|
|
795
|
-
while ((match = idRegex.exec(cleanExpr)) !== null) {
|
|
796
|
-
const id = match[1];
|
|
797
|
-
if (!isJsKeyword(id) && !locals.includes(id)) {
|
|
798
|
-
identifiers.push(id);
|
|
799
|
-
}
|
|
800
|
-
}
|
|
801
|
-
return { identifiers: [...new Set(identifiers)], locals: [...new Set(locals)] };
|
|
802
|
-
}
|
|
803
|
-
function isTemplateBuiltin(id) {
|
|
804
|
-
return TEMPLATE_BUILTINS.has(id);
|
|
805
|
-
}
|
|
806
|
-
function isJsKeyword(id) {
|
|
807
|
-
return JS_KEYWORDS.has(id);
|
|
808
|
-
}
|
|
809
|
-
function checkImport(node, violations, sourceFile) {
|
|
810
|
-
const moduleSpecifier = node.moduleSpecifier.getText(sourceFile).replace(/['\"]/g, "");
|
|
811
|
-
if (moduleSpecifier === "@king-design/vue" || moduleSpecifier === "@ksyun-internal/versatile") {
|
|
812
|
-
const namedBindings = node.importClause?.namedBindings;
|
|
813
|
-
if (namedBindings && ts__namespace.isNamedImports(namedBindings)) {
|
|
814
|
-
namedBindings.elements.forEach((element) => {
|
|
815
|
-
const originalName = element.propertyName ? element.propertyName.text : element.name.text;
|
|
816
|
-
const localName = element.name.text;
|
|
817
|
-
const isAlias = !!element.propertyName;
|
|
818
|
-
const isHook = originalName.startsWith("use") && originalName.length > 3 && originalName[3] === originalName[3].toUpperCase();
|
|
819
|
-
if (isHook) {
|
|
820
|
-
if (moduleSpecifier !== "@ksyun-internal/versatile") {
|
|
821
|
-
violations.push({
|
|
822
|
-
rule: `Hook ${originalName} \u5BFC\u5165\u6E90\u9519\u8BEF`,
|
|
823
|
-
match: `import { ${originalName} } from '${moduleSpecifier}'`,
|
|
824
|
-
suggestion: `\u5E94\u4ECE '@ksyun-internal/versatile' \u5BFC\u5165`
|
|
825
|
-
});
|
|
826
|
-
return;
|
|
827
|
-
}
|
|
828
|
-
if (!hooksRegistry.isKnownHook(originalName)) {
|
|
829
|
-
violations.push({
|
|
830
|
-
rule: "\u5F15\u7528\u4E86\u4E0D\u5B58\u5728\u7684 Hook",
|
|
831
|
-
match: originalName,
|
|
832
|
-
suggestion: `Hook ${originalName} \u4E0D\u5B58\u5728\u4E8E @ksyun-internal/versatile\u3002\u53EF\u7528\u7684 Hooks: ${hooksRegistry.getAllHookNames().join(", ") || "\u6682\u65E0"}`
|
|
833
|
-
});
|
|
834
|
-
return;
|
|
835
|
-
}
|
|
836
|
-
if (isAlias) {
|
|
837
|
-
violations.push({
|
|
838
|
-
rule: `\u7981\u6B62\u5BF9 Hook ${originalName} \u4F7F\u7528\u522B\u540D\u5BFC\u5165`,
|
|
839
|
-
match: `${originalName} as ${localName}`,
|
|
840
|
-
suggestion: `\u76F4\u63A5\u4F7F\u7528\u539F\u540D: import { ${originalName} } from '${moduleSpecifier}'`
|
|
841
|
-
});
|
|
842
|
-
}
|
|
843
|
-
return;
|
|
844
|
-
}
|
|
845
|
-
if (isAlias) {
|
|
846
|
-
violations.push({
|
|
847
|
-
rule: `\u7981\u6B62\u5BF9\u7EC4\u4EF6 ${originalName} \u4F7F\u7528\u522B\u540D\u5BFC\u5165`,
|
|
848
|
-
match: `${originalName} as ${localName}`,
|
|
849
|
-
suggestion: `\u76F4\u63A5\u4F7F\u7528\u539F\u540D: import { ${originalName} } from '${moduleSpecifier}'`
|
|
850
|
-
});
|
|
851
|
-
}
|
|
852
|
-
if (componentRegistry.isKnownComponent(originalName)) {
|
|
853
|
-
if (DUAL_EXPORT_COMPONENTS.has(originalName)) {
|
|
854
|
-
return;
|
|
855
|
-
}
|
|
856
|
-
const isVersatile = VERSATILE_COMPONENTS.has(originalName);
|
|
857
|
-
const expectedPackage = isVersatile ? "@ksyun-internal/versatile" : "@king-design/vue";
|
|
858
|
-
if (expectedPackage !== moduleSpecifier) {
|
|
859
|
-
violations.push({
|
|
860
|
-
rule: `\u7EC4\u4EF6 ${originalName} \u5BFC\u5165\u6E90\u9519\u8BEF`,
|
|
861
|
-
match: `import { ... } from '${moduleSpecifier}'`,
|
|
862
|
-
suggestion: `\u5E94\u4ECE '${expectedPackage}' \u5BFC\u5165`
|
|
863
|
-
});
|
|
864
|
-
}
|
|
865
|
-
} else {
|
|
866
|
-
violations.push({
|
|
867
|
-
rule: "\u5F15\u7528\u4E86\u4E0D\u5B58\u5728\u7684\u7EC4\u4EF6/\u5BFC\u51FA",
|
|
868
|
-
match: originalName,
|
|
869
|
-
suggestion: `\u8BF7\u786E\u8BA4 ${originalName} \u662F\u5426\u5B58\u5728\u4E8E ${moduleSpecifier}\u3002\u5EFA\u8BAE\u67E5\u770B\u6587\u6863\u3002`
|
|
870
|
-
});
|
|
871
|
-
}
|
|
872
|
-
});
|
|
873
|
-
}
|
|
874
|
-
}
|
|
875
|
-
}
|
|
876
|
-
function checkNestingRules(tagName, metadata, ancestors, violations) {
|
|
877
|
-
const requiredParents = metadata?.requiredParent ? [metadata.requiredParent] : NESTING_RULES[tagName];
|
|
878
|
-
if (!requiredParents || requiredParents.length === 0) return;
|
|
879
|
-
const hasValidAncestor = ancestors.some(
|
|
880
|
-
(ancestor) => requiredParents.includes(ancestor) || ancestor === "template"
|
|
881
|
-
);
|
|
882
|
-
if (!hasValidAncestor && ancestors.length > 0) {
|
|
883
|
-
violations.push({
|
|
884
|
-
rule: `${tagName} \u5FC5\u987B\u653E\u5728 ${requiredParents.join(" \u6216 ")} \u7EC4\u4EF6\u5185`,
|
|
885
|
-
match: tagName,
|
|
886
|
-
suggestion: `\u8BF7\u5C06 ${tagName} \u79FB\u52A8\u5230 ${requiredParents[0]} \u7EC4\u4EF6\u4E2D`
|
|
887
|
-
});
|
|
888
|
-
}
|
|
889
|
-
}
|
|
890
|
-
function checkComponentSpecificRules(tagName, node, violations) {
|
|
891
|
-
if (tagName === "Dropdown") {
|
|
892
|
-
node.children?.forEach((child) => {
|
|
893
|
-
if (child.type === 1 && child.tag === "template") {
|
|
894
|
-
const slotProp = child.props?.find(
|
|
895
|
-
(p) => p.type === 7 && p.name === "slot" && p.arg?.content === "menu"
|
|
896
|
-
);
|
|
897
|
-
if (slotProp) {
|
|
898
|
-
violations.push({
|
|
899
|
-
rule: "DropdownMenu \u4E0D\u80FD\u653E\u5728 #menu \u63D2\u69FD\u4E2D",
|
|
900
|
-
match: "<template #menu>",
|
|
901
|
-
suggestion: "\u8BF7\u79FB\u9664 <template #menu>\uFF0C\u5C06 DropdownMenu \u76F4\u63A5\u4F5C\u4E3A Dropdown \u7684\u7B2C\u4E8C\u4E2A\u5B50\u5143\u7D20"
|
|
902
|
-
});
|
|
903
|
-
}
|
|
904
|
-
}
|
|
905
|
-
});
|
|
906
|
-
}
|
|
907
|
-
if (tagName === "Table") {
|
|
908
|
-
node.props.forEach((prop) => {
|
|
909
|
-
if (prop.type === 6 && (prop.name === "rowKey" || prop.name === "row-key")) {
|
|
910
|
-
violations.push({
|
|
911
|
-
rule: "Table\u7EC4\u4EF6 rowKey \u5C5E\u6027 usage \u9519\u8BEF",
|
|
912
|
-
match: `rowKey="${prop.value?.content || ""}"`,
|
|
913
|
-
suggestion: 'rowKey \u5FC5\u987B\u662F\u4E00\u4E2A\u8FD4\u56DE\u552F\u4E00\u503C\u7684\u51FD\u6570\u3002\u8BF7\u4F7F\u7528\u7ED1\u5B9A\u8BED\u6CD5\uFF0C\u4F8B\u5982 :rowKey="(row) => row.id"'
|
|
914
|
-
});
|
|
915
|
-
}
|
|
916
|
-
});
|
|
917
|
-
}
|
|
918
|
-
if (tagName === "Icon") {
|
|
919
|
-
node.props.forEach((prop) => {
|
|
920
|
-
if (prop.type === 6 && prop.name === "name") {
|
|
921
|
-
violations.push({
|
|
922
|
-
rule: "Icon \u7EC4\u4EF6\u7981\u6B62\u4F7F\u7528 name \u5C5E\u6027",
|
|
923
|
-
match: `name="${prop.value?.content || ""}"`,
|
|
924
|
-
suggestion: '\u8BF7\u4F7F\u7528 class \u5C5E\u6027\u6307\u5B9A\u56FE\u6807\uFF0C\u4F8B\u5982: class="k-icon-search"'
|
|
925
|
-
});
|
|
926
|
-
}
|
|
927
|
-
});
|
|
928
|
-
}
|
|
929
|
-
}
|
|
930
|
-
function checkProps(tagName, node, metadata, violations) {
|
|
931
|
-
let customTagName = null;
|
|
932
|
-
const tagNameProp = node.props.find(
|
|
933
|
-
(p) => p.type === 6 && p.name === "tagName" || p.type === 7 && p.name === "bind" && p.arg?.content === "tagName"
|
|
934
|
-
);
|
|
935
|
-
if (tagNameProp?.type === 6 && tagNameProp.value?.content) {
|
|
936
|
-
customTagName = tagNameProp.value.content;
|
|
937
|
-
}
|
|
938
|
-
const dynamicAllowedProps = [];
|
|
939
|
-
if (customTagName === "a") {
|
|
940
|
-
dynamicAllowedProps.push("href", "target", "rel", "download");
|
|
941
|
-
}
|
|
942
|
-
node.props.forEach((prop) => {
|
|
943
|
-
if (prop.type !== 6) return;
|
|
944
|
-
const propName = prop.name;
|
|
945
|
-
const camelCaseProp = propName.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
946
|
-
const isPropValid = metadata.props?.some(
|
|
947
|
-
(p) => p.name === propName || p.name === camelCaseProp || p.name.toLowerCase() === propName.toLowerCase()
|
|
948
|
-
);
|
|
949
|
-
const isDynamicallyAllowed = dynamicAllowedProps.includes(propName);
|
|
950
|
-
const isBuiltinProp = ["class", "style", "key", "ref"].includes(propName) || propName.startsWith("data-");
|
|
951
|
-
const isNativeHtmlAttr = NATIVE_HTML_ATTRIBUTES.has(propName) || propName.startsWith("aria-");
|
|
952
|
-
if (!isPropValid && !isDynamicallyAllowed && !isBuiltinProp && !isNativeHtmlAttr) {
|
|
953
|
-
if (metadata.props && metadata.props.length > 0) {
|
|
954
|
-
violations.push({
|
|
955
|
-
rule: `\u5C5E\u6027 ${propName} \u4E0D\u5B58\u5728\u4E8E ${tagName}`,
|
|
956
|
-
match: propName,
|
|
957
|
-
suggestion: `\u53EF\u7528\u5C5E\u6027: ${metadata.props.map((p) => p.name).join(", ")}`
|
|
958
|
-
});
|
|
959
|
-
}
|
|
960
|
-
}
|
|
961
|
-
if (isPropValid && metadata.props) {
|
|
962
|
-
const targetProp = metadata.props.find(
|
|
963
|
-
(p) => p.name === propName || p.name === camelCaseProp || p.name.toLowerCase() === propName.toLowerCase()
|
|
964
|
-
);
|
|
965
|
-
if (targetProp) {
|
|
966
|
-
const attrValue = prop.value?.content;
|
|
967
|
-
if (attrValue) {
|
|
968
|
-
let cleanAllowedValues = [];
|
|
969
|
-
if (targetProp.allowedValues && targetProp.allowedValues.length > 0) {
|
|
970
|
-
cleanAllowedValues = targetProp.allowedValues.map((av) => String(av.value).replace(/['"]/g, ""));
|
|
971
|
-
} else if (targetProp.type?.kind === "union" && targetProp.type.unionTypes) {
|
|
972
|
-
cleanAllowedValues = targetProp.type.unionTypes.map((v) => String(v).replace(/['"]/g, ""));
|
|
973
|
-
}
|
|
974
|
-
if (cleanAllowedValues.length > 0 && !cleanAllowedValues.includes(attrValue)) {
|
|
975
|
-
violations.push({
|
|
976
|
-
rule: `${tagName} \u7684\u5C5E\u6027 ${propName} \u7684\u503C "${attrValue}" \u65E0\u6548`,
|
|
977
|
-
match: `${propName}="${attrValue}"`,
|
|
978
|
-
suggestion: `\u5141\u8BB8\u7684\u503C: ${cleanAllowedValues.join(" | ")}`
|
|
979
|
-
});
|
|
980
|
-
}
|
|
981
|
-
}
|
|
982
|
-
}
|
|
983
|
-
}
|
|
984
|
-
});
|
|
985
|
-
}
|
|
986
|
-
function checkEvents(tagName, node, metadata, violations) {
|
|
987
|
-
node.props.forEach((prop) => {
|
|
988
|
-
if (prop.type !== 7) return;
|
|
989
|
-
if (prop.name !== "on") return;
|
|
990
|
-
const arg = prop.arg;
|
|
991
|
-
const eventName = arg && "content" in arg ? arg.content : null;
|
|
992
|
-
if (!eventName) return;
|
|
993
|
-
if (eventName.startsWith("$change:") || eventName.startsWith("update:")) {
|
|
994
|
-
return;
|
|
995
|
-
}
|
|
996
|
-
const camelCaseEvent = eventName.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
997
|
-
const isEventValid = metadata.events?.some(
|
|
998
|
-
(e) => e.name === eventName || e.name === camelCaseEvent || e.name.toLowerCase() === eventName.toLowerCase()
|
|
999
|
-
);
|
|
1000
|
-
const isNativeEvent = NATIVE_EVENTS.has(eventName);
|
|
1001
|
-
if (!isEventValid && !isNativeEvent && metadata.events && metadata.events.length > 0) {
|
|
1002
|
-
violations.push({
|
|
1003
|
-
rule: `\u4E8B\u4EF6 @${eventName} \u4E0D\u5B58\u5728\u4E8E ${tagName}`,
|
|
1004
|
-
match: `@${eventName}`,
|
|
1005
|
-
suggestion: `\u53EF\u7528\u4E8B\u4EF6: ${metadata.events.map((e) => e.name).join(", ") || "\u65E0\u81EA\u5B9A\u4E49\u4E8B\u4EF6"}`
|
|
1006
|
-
});
|
|
1007
|
-
}
|
|
1008
|
-
});
|
|
1009
|
-
}
|
|
1010
|
-
function checkSlots(tagName, node, metadata, violations) {
|
|
1011
|
-
if (!node.children || !metadata.slots?.length) return;
|
|
1012
|
-
node.children.forEach((child) => {
|
|
1013
|
-
if (child.type !== 1 || child.tag !== "template") return;
|
|
1014
|
-
child.props.forEach((prop) => {
|
|
1015
|
-
if (prop.type !== 7 || prop.name !== "slot") return;
|
|
1016
|
-
const slotName = prop.arg?.content || "default";
|
|
1017
|
-
const isSlotValid = metadata.slots?.some((s) => s.name === slotName);
|
|
1018
|
-
if (!isSlotValid) {
|
|
1019
|
-
violations.push({
|
|
1020
|
-
rule: `\u63D2\u69FD #${slotName} \u4E0D\u5B58\u5728\u4E8E ${tagName}`,
|
|
1021
|
-
match: `#${slotName}`,
|
|
1022
|
-
suggestion: `\u53EF\u7528\u63D2\u69FD: ${metadata.slots.map((s) => s.name).join(", ")}`
|
|
1023
|
-
});
|
|
1024
|
-
}
|
|
1025
|
-
});
|
|
1026
|
-
});
|
|
1027
|
-
}
|
|
1028
|
-
function checkTemplate(node, violations, ancestors) {
|
|
1029
|
-
if (node.type === 1) {
|
|
1030
|
-
const elementNode = node;
|
|
1031
|
-
const tagName = elementNode.tag;
|
|
1032
|
-
const metadata = componentRegistry.isKnownComponent(tagName) ? componentRegistry.getComponent(tagName) : null;
|
|
1033
|
-
if (metadata) {
|
|
1034
|
-
checkNestingRules(tagName, metadata, ancestors, violations);
|
|
1035
|
-
}
|
|
1036
|
-
checkComponentSpecificRules(tagName, elementNode, violations);
|
|
1037
|
-
if (metadata) {
|
|
1038
|
-
checkProps(tagName, elementNode, metadata, violations);
|
|
1039
|
-
checkEvents(tagName, elementNode, metadata, violations);
|
|
1040
|
-
checkSlots(tagName, elementNode, metadata, violations);
|
|
1041
|
-
}
|
|
1042
|
-
}
|
|
1043
|
-
if ("children" in node && Array.isArray(node.children)) {
|
|
1044
|
-
const newAncestors = node.type === 1 && "tag" in node ? [...ancestors, node.tag] : ancestors;
|
|
1045
|
-
node.children.forEach((child) => {
|
|
1046
|
-
if (typeof child === "object" && child !== null && "type" in child) {
|
|
1047
|
-
checkTemplate(child, violations, newAncestors);
|
|
1048
|
-
}
|
|
1049
|
-
});
|
|
1050
|
-
}
|
|
1051
|
-
}
|
|
1052
|
-
|
|
1053
|
-
exports.analyzeCodeWithAST = analyzeCodeWithAST;
|
|
1054
|
-
exports.componentRegistry = componentRegistry;
|