css2class 2.0.0
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/API.md +1143 -0
- package/CHANGELOG.md +291 -0
- package/CONFIG.md +1096 -0
- package/CONTRIBUTING.md +571 -0
- package/MIGRATION.md +402 -0
- package/README.md +634 -0
- package/bin/class2css.js +380 -0
- package/class2css.config.js +124 -0
- package/common.css +3 -0
- package/configs/colors.config.js +62 -0
- package/configs/layout.config.js +110 -0
- package/configs/spacing.config.js +37 -0
- package/configs/typography.config.js +41 -0
- package/docs/.vitepress/config.mjs +65 -0
- package/docs/.vitepress/theme/custom.css +74 -0
- package/docs/.vitepress/theme/index.js +7 -0
- package/docs/guide/cli.md +97 -0
- package/docs/guide/concepts.md +63 -0
- package/docs/guide/config-template.md +365 -0
- package/docs/guide/config.md +275 -0
- package/docs/guide/faq.md +202 -0
- package/docs/guide/getting-started.md +83 -0
- package/docs/guide/important-and-static.md +67 -0
- package/docs/guide/incremental.md +162 -0
- package/docs/guide/rules-reference.md +354 -0
- package/docs/guide/units.md +57 -0
- package/docs/index.md +68 -0
- package/package.json +49 -0
- package/run.js +90 -0
- package/src/README.md +571 -0
- package/src/core/CacheManager.js +650 -0
- package/src/core/CompatibilityAdapter.js +264 -0
- package/src/core/ConfigManager.js +431 -0
- package/src/core/ConfigValidator.js +350 -0
- package/src/core/EventBus.js +77 -0
- package/src/core/FullScanManager.js +430 -0
- package/src/core/StateManager.js +631 -0
- package/src/docs/DocsServer.js +179 -0
- package/src/example.js +106 -0
- package/src/generators/DynamicClassGenerator.js +674 -0
- package/src/index.js +1046 -0
- package/src/parsers/ClassParser.js +572 -0
- package/src/parsers/ImportantParser.js +279 -0
- package/src/parsers/RegexCompiler.js +200 -0
- package/src/utils/ClassChangeTracker.js +366 -0
- package/src/utils/ConfigDiagnostics.js +673 -0
- package/src/utils/CssFormatter.js +261 -0
- package/src/utils/FileUtils.js +230 -0
- package/src/utils/Logger.js +150 -0
- package/src/utils/Throttle.js +172 -0
- package/src/utils/UnitProcessor.js +334 -0
- package/src/utils/WxssClassExtractor.js +137 -0
- package/src/watchers/ConfigWatcher.js +413 -0
- package/src/watchers/FileWatcher.js +133 -0
- package/src/writers/FileWriter.js +302 -0
- package/src/writers/UnifiedWriter.js +370 -0
- package/styles.config.js +250 -0
|
@@ -0,0 +1,572 @@
|
|
|
1
|
+
const UnitProcessor = require('../utils/UnitProcessor');
|
|
2
|
+
|
|
3
|
+
class ClassParser {
|
|
4
|
+
constructor(eventBus, regexCompiler, importantParser, userStaticClassSet, configManager) {
|
|
5
|
+
this.eventBus = eventBus;
|
|
6
|
+
this.regexCompiler = regexCompiler;
|
|
7
|
+
this.importantParser = importantParser;
|
|
8
|
+
this.userStaticClassSet = userStaticClassSet;
|
|
9
|
+
this.configManager = configManager;
|
|
10
|
+
|
|
11
|
+
// 初始化单位处理器
|
|
12
|
+
if (this.configManager) {
|
|
13
|
+
this.unitProcessor = new UnitProcessor(this.configManager.getConfig());
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// 优化的类名解析方法
|
|
18
|
+
parseClassOptimized(htmlStr) {
|
|
19
|
+
if (!htmlStr || typeof htmlStr !== 'string') {
|
|
20
|
+
this.eventBus.emit('parser:error', { error: 'Invalid HTML string provided' });
|
|
21
|
+
return { classArr: [], userStaticClassArr: [] };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const classList = new Set();
|
|
25
|
+
const userStaticList = new Set();
|
|
26
|
+
const compiledRegex = this.regexCompiler.getCompiledRegex();
|
|
27
|
+
|
|
28
|
+
if (!compiledRegex) {
|
|
29
|
+
this.eventBus.emit('parser:error', { error: 'Regex not compiled' });
|
|
30
|
+
return { classArr: [], userStaticClassArr: [] };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
let match;
|
|
35
|
+
const classAttrRegex = new RegExp(
|
|
36
|
+
compiledRegex.classAttr.source,
|
|
37
|
+
compiledRegex.classAttr.flags
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
while ((match = classAttrRegex.exec(htmlStr)) !== null) {
|
|
41
|
+
const classStr = match[1].slice(1, -1); // 移除引号
|
|
42
|
+
const classes = classStr.split(/\s+/).filter(Boolean);
|
|
43
|
+
|
|
44
|
+
for (const className of classes) {
|
|
45
|
+
const cleanName = this.importantParser.cleanImportantFlag(className);
|
|
46
|
+
|
|
47
|
+
// 提取 base class(处理响应式前缀如 sm:, md: 等)
|
|
48
|
+
// 按 : 拆分,取最后一段作为 base class
|
|
49
|
+
const baseClass = this.extractBaseClass(cleanName);
|
|
50
|
+
|
|
51
|
+
// 智能类名预处理和验证(使用 base class 进行分析)
|
|
52
|
+
const processedClass = this.preprocessClassName(className, cleanName);
|
|
53
|
+
|
|
54
|
+
// 先检查是否是静态类(使用 base class 检查)
|
|
55
|
+
if (this.userStaticClassSet.has(baseClass)) {
|
|
56
|
+
userStaticList.add(processedClass.original); // 保存原始 class 名(如 sm:flex)
|
|
57
|
+
this.eventBus.emit('parser:static:found', {
|
|
58
|
+
className: processedClass.original,
|
|
59
|
+
cleanName,
|
|
60
|
+
baseClass,
|
|
61
|
+
processed: processedClass,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// 再检查是否是动态类(使用 base class 检查)
|
|
66
|
+
if (baseClass.includes('-')) {
|
|
67
|
+
classList.add(processedClass.original); // 保存原始 class 名(如 sm:w-100)
|
|
68
|
+
this.eventBus.emit('parser:dynamic:found', {
|
|
69
|
+
className: processedClass.original,
|
|
70
|
+
cleanName,
|
|
71
|
+
baseClass,
|
|
72
|
+
processed: processedClass,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const result = {
|
|
79
|
+
userStaticClassArr: Array.from(userStaticList),
|
|
80
|
+
classArr: Array.from(classList),
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
this.eventBus.emit('parser:completed', {
|
|
84
|
+
staticCount: result.userStaticClassArr.length,
|
|
85
|
+
dynamicCount: result.classArr.length,
|
|
86
|
+
totalCount: result.userStaticClassArr.length + result.classArr.length,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
return result;
|
|
90
|
+
} catch (error) {
|
|
91
|
+
this.eventBus.emit('parser:error', { error: error.message });
|
|
92
|
+
return { classArr: [], userStaticClassArr: [] };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 提取 base class(去除响应式前缀等变体前缀)
|
|
97
|
+
extractBaseClass(className) {
|
|
98
|
+
if (!className || typeof className !== 'string') {
|
|
99
|
+
return className;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// 按 : 拆分,取最后一段作为 base class
|
|
103
|
+
// 例如: "sm:w-100" -> "w-100", "md:flex" -> "flex"
|
|
104
|
+
const parts = className.split(':');
|
|
105
|
+
return parts[parts.length - 1];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// 智能类名预处理
|
|
109
|
+
preprocessClassName(originalClassName, cleanName) {
|
|
110
|
+
const processed = {
|
|
111
|
+
original: originalClassName,
|
|
112
|
+
clean: cleanName,
|
|
113
|
+
hasImportant: originalClassName !== cleanName,
|
|
114
|
+
isValid: true,
|
|
115
|
+
warnings: [],
|
|
116
|
+
suggestions: [],
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
// 如果有单位处理器,进行智能分析
|
|
120
|
+
if (this.unitProcessor && cleanName.includes('-')) {
|
|
121
|
+
const parts = cleanName.split('-');
|
|
122
|
+
if (parts.length >= 2) {
|
|
123
|
+
const property = parts[0];
|
|
124
|
+
const value = parts.slice(1).join('-');
|
|
125
|
+
|
|
126
|
+
// 检查是否是已知的CSS属性
|
|
127
|
+
if (this.isKnownCSSProperty(property)) {
|
|
128
|
+
processed.property = property;
|
|
129
|
+
processed.value = value;
|
|
130
|
+
processed.cssProperty = this.getCSSPropertyMapping(property);
|
|
131
|
+
|
|
132
|
+
// 单位验证和建议
|
|
133
|
+
const unitAnalysis = this.analyzeUnit(property, value);
|
|
134
|
+
processed.unitAnalysis = unitAnalysis;
|
|
135
|
+
|
|
136
|
+
if (unitAnalysis.warnings.length > 0) {
|
|
137
|
+
processed.warnings.push(...unitAnalysis.warnings);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (unitAnalysis.suggestions.length > 0) {
|
|
141
|
+
processed.suggestions.push(...unitAnalysis.suggestions);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return processed;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// 检查是否是已知的CSS属性
|
|
151
|
+
isKnownCSSProperty(property) {
|
|
152
|
+
const knownProperties = [
|
|
153
|
+
'm',
|
|
154
|
+
'mt',
|
|
155
|
+
'mr',
|
|
156
|
+
'mb',
|
|
157
|
+
'ml',
|
|
158
|
+
'mx',
|
|
159
|
+
'my',
|
|
160
|
+
'p',
|
|
161
|
+
'pt',
|
|
162
|
+
'pr',
|
|
163
|
+
'pb',
|
|
164
|
+
'pl',
|
|
165
|
+
'px',
|
|
166
|
+
'py',
|
|
167
|
+
'w',
|
|
168
|
+
'h',
|
|
169
|
+
'max-w',
|
|
170
|
+
'max-h',
|
|
171
|
+
'min-w',
|
|
172
|
+
'min-h',
|
|
173
|
+
'text',
|
|
174
|
+
'font',
|
|
175
|
+
'leading',
|
|
176
|
+
'tracking',
|
|
177
|
+
'spacing',
|
|
178
|
+
'top',
|
|
179
|
+
'right',
|
|
180
|
+
'bottom',
|
|
181
|
+
'left',
|
|
182
|
+
'inset',
|
|
183
|
+
'rounded',
|
|
184
|
+
'border',
|
|
185
|
+
'bordert',
|
|
186
|
+
'borderr',
|
|
187
|
+
'borderb',
|
|
188
|
+
'borderl',
|
|
189
|
+
'opacity',
|
|
190
|
+
'op',
|
|
191
|
+
'transition',
|
|
192
|
+
'gap',
|
|
193
|
+
'size',
|
|
194
|
+
'b_r',
|
|
195
|
+
];
|
|
196
|
+
|
|
197
|
+
return knownProperties.includes(property);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// 获取CSS属性映射
|
|
201
|
+
getCSSPropertyMapping(property) {
|
|
202
|
+
const mappings = {
|
|
203
|
+
m: 'margin',
|
|
204
|
+
mt: 'margin-top',
|
|
205
|
+
mr: 'margin-right',
|
|
206
|
+
mb: 'margin-bottom',
|
|
207
|
+
ml: 'margin-left',
|
|
208
|
+
p: 'padding',
|
|
209
|
+
pt: 'padding-top',
|
|
210
|
+
pr: 'padding-right',
|
|
211
|
+
pb: 'padding-bottom',
|
|
212
|
+
pl: 'padding-left',
|
|
213
|
+
w: 'width',
|
|
214
|
+
h: 'height',
|
|
215
|
+
'max-w': 'max-width',
|
|
216
|
+
'max-h': 'max-height',
|
|
217
|
+
'min-w': 'min-width',
|
|
218
|
+
'min-h': 'min-height',
|
|
219
|
+
text: 'font-size',
|
|
220
|
+
font: 'font-weight',
|
|
221
|
+
leading: 'line-height',
|
|
222
|
+
tracking: 'letter-spacing',
|
|
223
|
+
top: 'top',
|
|
224
|
+
right: 'right',
|
|
225
|
+
bottom: 'bottom',
|
|
226
|
+
left: 'left',
|
|
227
|
+
rounded: 'border-radius',
|
|
228
|
+
border: 'border-width',
|
|
229
|
+
opacity: 'opacity',
|
|
230
|
+
gap: 'gap',
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
return mappings[property] || property;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// 分析单位使用
|
|
237
|
+
analyzeUnit(property, value) {
|
|
238
|
+
const analysis = {
|
|
239
|
+
warnings: [],
|
|
240
|
+
suggestions: [],
|
|
241
|
+
recommendedUnit: null,
|
|
242
|
+
detectedUnit: null,
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
if (!this.unitProcessor) {
|
|
246
|
+
return analysis;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// 检测当前单位
|
|
250
|
+
const detectedUnit = this.unitProcessor.extractUnit(value);
|
|
251
|
+
analysis.detectedUnit = detectedUnit;
|
|
252
|
+
|
|
253
|
+
// 获取推荐单位
|
|
254
|
+
const cssProperty = this.getCSSPropertyMapping(property);
|
|
255
|
+
const recommendedUnit = this.unitProcessor.getPropertyUnit(cssProperty);
|
|
256
|
+
analysis.recommendedUnit = recommendedUnit;
|
|
257
|
+
|
|
258
|
+
// 单位一致性检查
|
|
259
|
+
if (detectedUnit && recommendedUnit && detectedUnit !== recommendedUnit) {
|
|
260
|
+
analysis.warnings.push(`单位不一致: 使用了${detectedUnit},建议使用${recommendedUnit}`);
|
|
261
|
+
analysis.suggestions.push(
|
|
262
|
+
`考虑使用 ${property}-${value.replace(detectedUnit, '')}${recommendedUnit ? recommendedUnit : ''}`
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// 无单位值检查
|
|
267
|
+
if (!detectedUnit && recommendedUnit) {
|
|
268
|
+
const numericValue = this.unitProcessor.extractNumericValue(value);
|
|
269
|
+
if (numericValue !== null) {
|
|
270
|
+
analysis.suggestions.push(`建议明确单位: ${property}-${value}${recommendedUnit}`);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return analysis;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// 向后兼容的解析方法
|
|
278
|
+
parseClass(htmlStr) {
|
|
279
|
+
const result = this.parseClassOptimized(htmlStr);
|
|
280
|
+
|
|
281
|
+
// 去重并排序
|
|
282
|
+
const uniqueStaticClasses = [...new Set(result.userStaticClassArr)].sort();
|
|
283
|
+
const uniqueDynamicClasses = [...new Set(result.classArr)].sort();
|
|
284
|
+
|
|
285
|
+
this.eventBus.emit('parser:log', {
|
|
286
|
+
classArr: uniqueDynamicClasses,
|
|
287
|
+
userStaticClassArr: uniqueStaticClasses,
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
return {
|
|
291
|
+
userStaticClassArr: uniqueStaticClasses,
|
|
292
|
+
classArr: uniqueDynamicClasses,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// 批量解析多个文件
|
|
297
|
+
async batchParseFiles(files, cacheManager) {
|
|
298
|
+
const results = [];
|
|
299
|
+
const totalFiles = files.length;
|
|
300
|
+
|
|
301
|
+
this.eventBus.emit('parser:batch:started', { totalFiles });
|
|
302
|
+
|
|
303
|
+
for (let i = 0; i < files.length; i++) {
|
|
304
|
+
const filePath = files[i];
|
|
305
|
+
try {
|
|
306
|
+
const html = await cacheManager.getFileContent(filePath);
|
|
307
|
+
if (html) {
|
|
308
|
+
const classInfo = this.parseClassOptimized(html);
|
|
309
|
+
results.push({
|
|
310
|
+
filePath,
|
|
311
|
+
success: true,
|
|
312
|
+
classInfo,
|
|
313
|
+
staticCount: classInfo.userStaticClassArr.length,
|
|
314
|
+
dynamicCount: classInfo.classArr.length,
|
|
315
|
+
});
|
|
316
|
+
} else {
|
|
317
|
+
results.push({
|
|
318
|
+
filePath,
|
|
319
|
+
success: false,
|
|
320
|
+
error: 'Failed to read file content',
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
} catch (error) {
|
|
324
|
+
results.push({
|
|
325
|
+
filePath,
|
|
326
|
+
success: false,
|
|
327
|
+
error: error.message,
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// 进度报告
|
|
332
|
+
if ((i + 1) % 10 === 0 || i === files.length - 1) {
|
|
333
|
+
this.eventBus.emit('parser:batch:progress', {
|
|
334
|
+
processed: i + 1,
|
|
335
|
+
total: totalFiles,
|
|
336
|
+
percentage: Math.round(((i + 1) / totalFiles) * 100),
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const successCount = results.filter((r) => r.success).length;
|
|
342
|
+
const totalStaticClasses = results
|
|
343
|
+
.filter((r) => r.success)
|
|
344
|
+
.reduce((sum, r) => sum + r.staticCount, 0);
|
|
345
|
+
const totalDynamicClasses = results
|
|
346
|
+
.filter((r) => r.success)
|
|
347
|
+
.reduce((sum, r) => sum + r.dynamicCount, 0);
|
|
348
|
+
|
|
349
|
+
this.eventBus.emit('parser:batch:completed', {
|
|
350
|
+
totalFiles,
|
|
351
|
+
successCount,
|
|
352
|
+
failedCount: totalFiles - successCount,
|
|
353
|
+
totalStaticClasses,
|
|
354
|
+
totalDynamicClasses,
|
|
355
|
+
results,
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
return results;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// 解析单个文件
|
|
362
|
+
async parseFile(filePath, cacheManager) {
|
|
363
|
+
try {
|
|
364
|
+
const html = await cacheManager.getFileContent(filePath);
|
|
365
|
+
if (!html) {
|
|
366
|
+
this.eventBus.emit('parser:file:error', { filePath, error: 'Empty file content' });
|
|
367
|
+
return null;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const classInfo = this.parseClassOptimized(html);
|
|
371
|
+
|
|
372
|
+
this.eventBus.emit('parser:file:completed', {
|
|
373
|
+
filePath,
|
|
374
|
+
staticCount: classInfo.userStaticClassArr.length,
|
|
375
|
+
dynamicCount: classInfo.classArr.length,
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
return classInfo;
|
|
379
|
+
} catch (error) {
|
|
380
|
+
this.eventBus.emit('parser:file:error', { filePath, error: error.message });
|
|
381
|
+
return null;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// 验证解析结果
|
|
386
|
+
validateParseResult(result) {
|
|
387
|
+
const errors = [];
|
|
388
|
+
const warnings = [];
|
|
389
|
+
|
|
390
|
+
if (!result) {
|
|
391
|
+
errors.push('Parse result is null or undefined');
|
|
392
|
+
return { errors, warnings, isValid: false };
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (!Array.isArray(result.classArr)) {
|
|
396
|
+
errors.push('classArr must be an array');
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if (!Array.isArray(result.userStaticClassArr)) {
|
|
400
|
+
errors.push('userStaticClassArr must be an array');
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// 检查重复的类名
|
|
404
|
+
const allClasses = [...result.classArr, ...result.userStaticClassArr];
|
|
405
|
+
const duplicates = allClasses.filter(
|
|
406
|
+
(className, index) => allClasses.indexOf(className) !== index
|
|
407
|
+
);
|
|
408
|
+
|
|
409
|
+
if (duplicates.length > 0) {
|
|
410
|
+
warnings.push(`Duplicate class names found: ${duplicates.join(', ')}`);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// 检查无效的类名
|
|
414
|
+
const invalidClasses = allClasses.filter((className) => {
|
|
415
|
+
return !className || typeof className !== 'string' || className.trim().length === 0;
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
if (invalidClasses.length > 0) {
|
|
419
|
+
errors.push(`Invalid class names found: ${invalidClasses.join(', ')}`);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
return { errors, warnings, isValid: errors.length === 0 };
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// 更新配置(用于配置热更新)
|
|
426
|
+
updateConfig(newConfigManager) {
|
|
427
|
+
if (newConfigManager && newConfigManager !== this.configManager) {
|
|
428
|
+
this.configManager = newConfigManager;
|
|
429
|
+
|
|
430
|
+
// 重新初始化单位处理器
|
|
431
|
+
this.unitProcessor = new UnitProcessor(this.configManager.getConfig());
|
|
432
|
+
|
|
433
|
+
this.eventBus.emit('parser:config:updated', {
|
|
434
|
+
timestamp: Date.now(),
|
|
435
|
+
hasUnitProcessor: !!this.unitProcessor,
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// 更新静态类集合
|
|
441
|
+
updateUserStaticClassSet(newUserStaticClassSet) {
|
|
442
|
+
if (newUserStaticClassSet && newUserStaticClassSet !== this.userStaticClassSet) {
|
|
443
|
+
const oldSize = this.userStaticClassSet.size;
|
|
444
|
+
this.userStaticClassSet = newUserStaticClassSet;
|
|
445
|
+
const newSize = this.userStaticClassSet.size;
|
|
446
|
+
|
|
447
|
+
this.eventBus.emit('parser:static:updated', {
|
|
448
|
+
oldSize,
|
|
449
|
+
newSize,
|
|
450
|
+
delta: newSize - oldSize,
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// 获取增强的解析统计
|
|
456
|
+
getParseStats() {
|
|
457
|
+
const baseStats = {
|
|
458
|
+
userStaticClassSetSize: this.userStaticClassSet.size,
|
|
459
|
+
regexCompilerReady: !!this.regexCompiler.getCompiledRegex(),
|
|
460
|
+
importantParserReady: !!this.importantParser,
|
|
461
|
+
unitProcessorReady: !!this.unitProcessor,
|
|
462
|
+
configManagerReady: !!this.configManager,
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
// 如果有单位处理器,添加单位处理统计
|
|
466
|
+
if (this.unitProcessor) {
|
|
467
|
+
const unitConfig = this.unitProcessor.getConfig();
|
|
468
|
+
baseStats.unitProcessor = {
|
|
469
|
+
baseUnit: unitConfig.baseUnit,
|
|
470
|
+
unitConversion: unitConfig.unitConversion,
|
|
471
|
+
supportedProperties: Object.keys(unitConfig.propertyUnits).length,
|
|
472
|
+
unitlessProperties: unitConfig.unitlessProperties.length,
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
return baseStats;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// 获取类名建议
|
|
480
|
+
getClassNameSuggestions(partialClassName) {
|
|
481
|
+
const suggestions = [];
|
|
482
|
+
|
|
483
|
+
if (!partialClassName || typeof partialClassName !== 'string') {
|
|
484
|
+
return suggestions;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
const partial = partialClassName.toLowerCase();
|
|
488
|
+
|
|
489
|
+
// 从静态类中搜索
|
|
490
|
+
for (const staticClass of this.userStaticClassSet) {
|
|
491
|
+
if (staticClass.toLowerCase().includes(partial)) {
|
|
492
|
+
suggestions.push({
|
|
493
|
+
type: 'static',
|
|
494
|
+
className: staticClass,
|
|
495
|
+
description: 'Static utility class',
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// 如果是动态类的开始,提供属性建议
|
|
501
|
+
if (partial.includes('-')) {
|
|
502
|
+
const parts = partial.split('-');
|
|
503
|
+
const property = parts[0];
|
|
504
|
+
|
|
505
|
+
if (this.isKnownCSSProperty(property)) {
|
|
506
|
+
const cssProperty = this.getCSSPropertyMapping(property);
|
|
507
|
+
const recommendedUnit = this.unitProcessor?.getPropertyUnit(cssProperty);
|
|
508
|
+
|
|
509
|
+
suggestions.push({
|
|
510
|
+
type: 'dynamic',
|
|
511
|
+
property,
|
|
512
|
+
cssProperty,
|
|
513
|
+
recommendedUnit,
|
|
514
|
+
examples: this.generateExamples(property, recommendedUnit),
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
return suggestions.slice(0, 10); // 限制建议数量
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// 生成使用示例
|
|
523
|
+
generateExamples(property, unit) {
|
|
524
|
+
const examples = [];
|
|
525
|
+
const baseValues = ['4', '8', '12', '16', '20'];
|
|
526
|
+
|
|
527
|
+
baseValues.forEach((value) => {
|
|
528
|
+
examples.push(`${property}-${value}${unit || ''}`);
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
return examples;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// 性能分析
|
|
535
|
+
analyzePerformance(htmlStr) {
|
|
536
|
+
const startTime = process.hrtime.bigint();
|
|
537
|
+
|
|
538
|
+
const result = this.parseClassOptimized(htmlStr);
|
|
539
|
+
|
|
540
|
+
const endTime = process.hrtime.bigint();
|
|
541
|
+
const duration = Number(endTime - startTime) / 1000000; // 转换为毫秒
|
|
542
|
+
|
|
543
|
+
return {
|
|
544
|
+
result,
|
|
545
|
+
performance: {
|
|
546
|
+
duration,
|
|
547
|
+
classesPerMs: (result.classArr.length + result.userStaticClassArr.length) / duration,
|
|
548
|
+
htmlSize: htmlStr.length,
|
|
549
|
+
classesFound: result.classArr.length + result.userStaticClassArr.length,
|
|
550
|
+
},
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// 调试解析过程
|
|
555
|
+
debugParse(htmlStr, maxLength = 1000) {
|
|
556
|
+
const truncatedHtml =
|
|
557
|
+
htmlStr.length > maxLength ? htmlStr.substring(0, maxLength) + '...' : htmlStr;
|
|
558
|
+
|
|
559
|
+
const result = this.parseClassOptimized(htmlStr);
|
|
560
|
+
const validation = this.validateParseResult(result);
|
|
561
|
+
|
|
562
|
+
return {
|
|
563
|
+
inputLength: htmlStr.length,
|
|
564
|
+
truncatedInput: truncatedHtml,
|
|
565
|
+
result,
|
|
566
|
+
validation,
|
|
567
|
+
stats: this.getParseStats(),
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
module.exports = ClassParser;
|