@compelem/compiler 0.30.0-beta.4 → 0.30.0-beta.5

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/index.d.ts CHANGED
@@ -273,6 +273,73 @@ interface VarMapEntry {
273
273
  declare function defaultInclude(id: string, code: string): boolean;
274
274
  /** 编译单个文件。解析失败时原样返回;静态分析/模板 codegen 失败会抛 StaticAnalysisError。 */
275
275
  declare function compileFile(code: string, id: string, options?: CompilerOptions): CompileResult;
276
+ /**
277
+ * 编译**已确定要编译**的源码,跳过 `include` 判定。
278
+ *
279
+ * 与 {@link compileFile} 的分工:
280
+ * - `compileFile` 面向「磁盘上可能是组件、也可能不是」的输入,先按扩展名 +
281
+ * 特征做 include 判定;
282
+ * - `compileSource` 面向「调用方已经决定要编译」的输入 —— 典型是 SFC 插件把
283
+ * `.ce` 分段拼成的虚拟 TypeScript 模块。虚拟模块的 id 仍是 `.ce`(为了
284
+ * sourcemap 指向真实文件),天然过不了 `defaultInclude` 的扩展名检查,
285
+ * 以前只能传 `include: () => true` 绕过整个判定(连 node_modules 排除也一起绕过)。
286
+ *
287
+ * `options.include` 在这里被忽略。
288
+ */
289
+ declare function compileSource(code: string, id: string, options?: CompilerOptions): CompileResult;
290
+ //#endregion
291
+ //#region src/diagnostics.d.ts
292
+ /** 约定违规的处理力度:error 阻断构建 / warn 控制台提示 / silent 忽略。 */
293
+ type ConventionMode = 'error' | 'warn' | 'silent';
294
+ /** 已格式化为 `文件:行:列 [类名] 消息` 的两组诊断文本。 */
295
+ interface CollectedDiagnostics {
296
+ errs: string[];
297
+ warns: string[];
298
+ }
299
+ /** 惰性构建换行偏移索引(升序;仅在有诊断时付费一次)。 */
300
+ declare function newlineIndex(code: string): Int32Array;
301
+ /** 把源码偏移换算成 `行:列`(1 起算);带换行索引时走二分(O(log n))。 */
302
+ declare function offsetToLineCol(code: string, offset: number, nl?: Int32Array): string;
303
+ /** 相对 root 的展示路径(统一正斜杠,便于点击跳转)。 */
304
+ declare function relativeToRoot(id: string, root: string): string;
305
+ /**
306
+ * 从 diagnostics 收集**约定诊断**为可上报文本,按 severity 分为 errs(E-*)与 warns(W-*)。
307
+ *
308
+ * `root` 只用于把绝对路径压成相对路径,不影响判定。
309
+ */
310
+ declare function collectDiagnostics(res: CompileResult, code: string, id: string, root: string): CollectedDiagnostics;
311
+ /**
312
+ * 按 mode 上报违规:error 走插件上下文的 `this.error`(阻断构建),warn 走控制台。
313
+ *
314
+ * `thisArg` 是 Rollup/Vite 的插件上下文;纯 API 调用时传 undefined 也能工作
315
+ * (退化为 `throw`)。
316
+ */
317
+ declare function reportDiagnostics(msgs: string[], mode: ConventionMode, head: string, thisArg: any): void;
318
+ /**
319
+ * 一站式上报:W 码恒为控制台提示(不随档位阻断),E 码按 mode 处理。
320
+ *
321
+ * 返回是否发生了阻断(仅用于调用方决定是否继续)。
322
+ */
323
+ declare function reportConventionDiagnostics(collected: CollectedDiagnostics, mode: ConventionMode, thisArg: any, tag?: string): void;
324
+ //#endregion
325
+ //#region src/tag-registry.d.ts
326
+ /** `@tag('x-foo')` / `@tag(\`x-foo\`)`;一次扫描内复用,调用方负责重置 lastIndex。 */
327
+ declare const TAG_RE: RegExp;
328
+ /** 默认只把 `.ts` / `.tsx` 当作组件来源。 */
329
+ declare const DEFAULT_TAG_EXT: RegExp;
330
+ interface ScanKnownTagsOptions {
331
+ /** 参与扫描的文件扩展名正则,默认 {@link DEFAULT_TAG_EXT}。 */
332
+ extensions?: RegExp;
333
+ /** 目录递归深度上限,默认 20。 */
334
+ maxDepth?: number;
335
+ }
336
+ /**
337
+ * 递归收集 root 下匹配 `extensions` 的文件里的 `@tag` 名,写入 `out`。
338
+ *
339
+ * 读失败的文件直接跳过:注册表是 best-effort 的,它只会**放宽**校验,
340
+ * 不该让一次权限错误打断构建。`node_modules` 等目录不参与扫描。
341
+ */
342
+ declare function scanKnownTags(root: string, out: Set<string>, options?: ScanKnownTagsOptions): void;
276
343
  //#endregion
277
- export { type CompileResult, type CompiledStatic, type CompilerOptions, type ComponentAnalysis, DEBUG_DIV, type DepSummary, type ReactiveField, type ReactiveKind, type RenderAnalysis, StaticAnalysisError, type VitePluginOptions, analyzeFile, analyzeRender, assertStaticAnalysis, buildStaticLiteral, collectCompelemImports, compelemCompiler, compileFile, defaultInclude, injectInto, inlineTemplateMethods, isCompelemSource, parse };
344
+ export { type CollectedDiagnostics, type CompileResult, type CompiledStatic, type CompilerOptions, type ComponentAnalysis, type ConventionMode, DEBUG_DIV, DEFAULT_TAG_EXT, type DepSummary, type ReactiveField, type ReactiveKind, type RenderAnalysis, type ScanKnownTagsOptions, StaticAnalysisError, TAG_RE, type VitePluginOptions, analyzeFile, analyzeRender, assertStaticAnalysis, buildStaticLiteral, collectCompelemImports, collectDiagnostics, compelemCompiler, compileFile, compileSource, defaultInclude, injectInto, inlineTemplateMethods, isCompelemSource, newlineIndex, offsetToLineCol, parse, relativeToRoot, reportConventionDiagnostics, reportDiagnostics, scanKnownTags };
278
345
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- import { a as buildStaticLiteral, c as StaticAnalysisError, d as analyzeRender, f as analyzeFile, h as parse, i as defaultInclude, l as assertStaticAnalysis, m as isCompelemSource, o as injectInto, p as collectCompelemImports, r as compileFile, s as DEBUG_DIV, t as compelemCompiler, u as inlineTemplateMethods } from "./vite-CY_6E-Ut.js";
1
+ import { C as collectCompelemImports, S as analyzeFile, T as parse, _ as DEBUG_DIV, a as scanKnownTags, b as inlineTemplateMethods, c as offsetToLineCol, d as reportDiagnostics, f as compileFile, g as injectInto, h as buildStaticLiteral, i as TAG_RE, l as relativeToRoot, m as defaultInclude, o as collectDiagnostics, p as compileSource, r as DEFAULT_TAG_EXT, s as newlineIndex, t as compelemCompiler, u as reportConventionDiagnostics, v as StaticAnalysisError, w as isCompelemSource, x as analyzeRender, y as assertStaticAnalysis } from "./vite-CLK9td6W.js";
2
2
 
3
- export { DEBUG_DIV, StaticAnalysisError, analyzeFile, analyzeRender, assertStaticAnalysis, buildStaticLiteral, collectCompelemImports, compelemCompiler, compileFile, defaultInclude, injectInto, inlineTemplateMethods, isCompelemSource, parse };
3
+ export { DEBUG_DIV, DEFAULT_TAG_EXT, StaticAnalysisError, TAG_RE, analyzeFile, analyzeRender, assertStaticAnalysis, buildStaticLiteral, collectCompelemImports, collectDiagnostics, compelemCompiler, compileFile, compileSource, defaultInclude, injectInto, inlineTemplateMethods, isCompelemSource, newlineIndex, offsetToLineCol, parse, relativeToRoot, reportConventionDiagnostics, reportDiagnostics, scanKnownTags };
@@ -5287,78 +5287,93 @@ function checkTemplateRules(tree, tmpl, comp, ctx, knownTags) {
5287
5287
  });
5288
5288
  }
5289
5289
  }
5290
- for (const el of elements) for (const a of el.attrs) {
5291
- if (a.name.includes("⟬")) continue;
5292
- const varPart = a.parts.find((p) => p.type === "var");
5293
- const varCount = a.parts.filter((p) => p.type === "var").length;
5294
- if (a.name.startsWith("@") && !a.name.startsWith("@mutate")) {
5295
- const segs = a.name.slice(1).split(".");
5296
- const evName = (segs[0] ?? "").toLowerCase();
5297
- const mods = segs.slice(1);
5298
- const compEmits = localEmits.get(el.tag.toLowerCase());
5299
- if (!!compEmits && matchEmitName(compEmits, evName)) {
5300
- const bad = mods.filter((mo) => !COMP_EVENT_MODS.has(mo) && !EVENT_FLOW_MOD_RE.test(mo));
5301
- if (bad.length) errs.push({
5290
+ for (const el of elements) {
5291
+ const seenDirectives = /* @__PURE__ */ new Set();
5292
+ for (const a of el.attrs) {
5293
+ if (!a.name.includes("⟬")) continue;
5294
+ const dm = /⟬Ċ⟭(\d+)/.exec(a.name);
5295
+ if (!dm) continue;
5296
+ const dv = tmpl.vars[+dm[1]];
5297
+ if (!dv?.isDirectiveCall || !dv.directiveName) continue;
5298
+ if (seenDirectives.has(dv.directiveName)) errs.push({
5299
+ ...pos,
5300
+ message: `E-DIRECTIVE-DUP: 元素 <${el.tag}> 上的指令 '${dv.directiveName}' 出现了两次以上;同一元素每种指令只能写一次(重复的那份运行期会被静默丢弃)`
5301
+ });
5302
+ else seenDirectives.add(dv.directiveName);
5303
+ }
5304
+ for (const a of el.attrs) {
5305
+ if (a.name.includes("⟬")) continue;
5306
+ const varPart = a.parts.find((p) => p.type === "var");
5307
+ const varCount = a.parts.filter((p) => p.type === "var").length;
5308
+ if (a.name.startsWith("@") && !a.name.startsWith("@mutate")) {
5309
+ const segs = a.name.slice(1).split(".");
5310
+ const evName = (segs[0] ?? "").toLowerCase();
5311
+ const mods = segs.slice(1);
5312
+ const compEmits = localEmits.get(el.tag.toLowerCase());
5313
+ if (!!compEmits && matchEmitName(compEmits, evName)) {
5314
+ const bad = mods.filter((mo) => !COMP_EVENT_MODS.has(mo) && !EVENT_FLOW_MOD_RE.test(mo));
5315
+ if (bad.length) errs.push({
5316
+ ...pos,
5317
+ severity: "warn",
5318
+ message: `W-EVENT-COMP-MODIFIER: '${a.name}' 是组件事件(${el.tag} 的 @emits 已声明 '${evName}'),运行期只应用 debounce/throttle/once,[${bad.join(", ")}] 会被静默丢弃`
5319
+ });
5320
+ } else if (!KEY_EVENTS.has(evName) && NATIVE_EVENTS.has(evName)) {
5321
+ const bad = mods.filter((mo) => !EVENT_PLAIN_MODS.has(mo) && !EVENT_FLOW_MOD_RE.test(mo));
5322
+ if (bad.length) errs.push({
5323
+ ...pos,
5324
+ message: `E-EVENT-MODIFIER: '${a.name}' 中的 [${bad.join(", ")}] 不是内置修饰符;运行期会把它当作 KeyboardEvent.key 过滤,而 '${evName}' 不是键盘事件 ⇒ 处理器恒不触发`
5325
+ });
5326
+ }
5327
+ if (varCount === 0 && mods.length === 0) errs.push({
5302
5328
  ...pos,
5303
5329
  severity: "warn",
5304
- message: `W-EVENT-COMP-MODIFIER: '${a.name}' 是组件事件(${el.tag} 的 @emits 已声明 '${evName}'),运行期只应用 debounce/throttle/once,[${bad.join(", ")}] 会被静默丢弃`
5305
- });
5306
- } else if (!KEY_EVENTS.has(evName) && NATIVE_EVENTS.has(evName)) {
5307
- const bad = mods.filter((mo) => !EVENT_PLAIN_MODS.has(mo) && !EVENT_FLOW_MOD_RE.test(mo));
5308
- if (bad.length) errs.push({
5309
- ...pos,
5310
- message: `E-EVENT-MODIFIER: '${a.name}' 中的 [${bad.join(", ")}] 不是内置修饰符;运行期会把它当作 KeyboardEvent.key 过滤,而 '${evName}' 不是键盘事件 ⇒ 处理器恒不触发`
5330
+ message: `W-EVENT-NO-HANDLER: '${a.name}' 没有插值处理器,运行期只注册空监听器(noop)——是否遗漏了 \${…}?`
5311
5331
  });
5312
5332
  }
5313
- if (varCount === 0 && mods.length === 0) errs.push({
5314
- ...pos,
5315
- severity: "warn",
5316
- message: `W-EVENT-NO-HANDLER: '${a.name}' 没有插值处理器,运行期只注册空监听器(noop)——是否遗漏了 \${…}?`
5317
- });
5318
- }
5319
- if (a.name.startsWith(".") && varCount === 0) {
5320
- errs.push({
5321
- ...pos,
5322
- message: `E-PROP-INTERP: Prop '${a.name}' 必须是插值(.foo="\${expr}");静态值请直接写属性或改用 bind 指令`
5323
- });
5324
- continue;
5325
- }
5326
- if (a.name.startsWith("@mutate")) {
5327
- if (!a.name.slice(1).split(".").slice(1).some((mo) => MUTATE_TYPE_MODS.has(mo))) errs.push({
5328
- ...pos,
5329
- message: `E-MUTATE-MODIFIER: 事件 '${a.name}' 缺少变异类型修饰符,必须使用 @mutate.attr/child/char/tree 之一(裸 @mutate 运行期无法 observe)`
5330
- });
5331
- }
5332
- if (a.name.startsWith("@") && varCount === 1 && varPart) {
5333
- const exprSource = tmpl.vars[varPart.index]?.exprSource ?? "";
5334
- if (INLINE_FN_RE.test(exprSource)) continue;
5335
- const m = THIS_MEMBER_RE.exec(exprSource);
5336
- if (m) {
5337
- const name = m[1];
5338
- if (members === null || members.has(name) || KNOWN_BASE_MEMBERS.has(name)) continue;
5333
+ if (a.name.startsWith(".") && varCount === 0) {
5339
5334
  errs.push({
5340
5335
  ...pos,
5341
- message: `E-EVENT-FN: 事件 '${a.name}' 的处理器 '${exprSource}' 不是组件 ${comp.className} 的成员`
5336
+ message: `E-PROP-INTERP: Prop '${a.name}' 必须是插值(.foo="\${expr}");静态值请直接写属性或改用 bind 指令`
5342
5337
  });
5338
+ continue;
5343
5339
  }
5344
- continue;
5345
- }
5346
- if (a.name === "ref" && varCount === 1 && varPart) {
5347
- const exprSource = tmpl.vars[varPart.index]?.exprSource ?? "";
5348
- const m = THIS_MEMBER_RE.exec(exprSource);
5349
- if (m && members !== null) {
5350
- const name = m[1];
5351
- if (!members.has(name)) errs.push({
5340
+ if (a.name.startsWith("@mutate")) {
5341
+ if (!a.name.slice(1).split(".").slice(1).some((mo) => MUTATE_TYPE_MODS.has(mo))) errs.push({
5352
5342
  ...pos,
5353
- message: `E-REF-TYPE: ref 绑定的 '${exprSource}' 不是组件 ${comp.className} 的成员`
5343
+ message: `E-MUTATE-MODIFIER: 事件 '${a.name}' 缺少变异类型修饰符,必须使用 @mutate.attr/child/char/tree 之一(裸 @mutate 运行期无法 observe)`
5354
5344
  });
5355
- else {
5356
- const member = findMemberNode(comp, ctx, name);
5357
- if (member?.type === "PropertyDefinition" && member.value != null) {
5358
- if (!isCreateRefCall(member.value, comp)) errs.push({
5359
- ...pos,
5360
- message: `E-REF-TYPE: ref 绑定的 '${exprSource}' 初始化器不是 createRef() 调用,运行时将报「Ref must be a RefObject」`
5361
- });
5345
+ }
5346
+ if (a.name.startsWith("@") && varCount === 1 && varPart) {
5347
+ const exprSource = tmpl.vars[varPart.index]?.exprSource ?? "";
5348
+ if (INLINE_FN_RE.test(exprSource)) continue;
5349
+ const m = THIS_MEMBER_RE.exec(exprSource);
5350
+ if (m) {
5351
+ const name = m[1];
5352
+ if (members === null || members.has(name) || KNOWN_BASE_MEMBERS.has(name)) continue;
5353
+ errs.push({
5354
+ ...pos,
5355
+ message: `E-EVENT-FN: 事件 '${a.name}' 的处理器 '${exprSource}' 不是组件 ${comp.className} 的成员`
5356
+ });
5357
+ }
5358
+ continue;
5359
+ }
5360
+ if (a.name === "ref" && varCount === 1 && varPart) {
5361
+ const exprSource = tmpl.vars[varPart.index]?.exprSource ?? "";
5362
+ const m = THIS_MEMBER_RE.exec(exprSource);
5363
+ if (m && members !== null) {
5364
+ const name = m[1];
5365
+ if (!members.has(name)) errs.push({
5366
+ ...pos,
5367
+ message: `E-REF-TYPE: ref 绑定的 '${exprSource}' 不是组件 ${comp.className} 的成员`
5368
+ });
5369
+ else {
5370
+ const member = findMemberNode(comp, ctx, name);
5371
+ if (member?.type === "PropertyDefinition" && member.value != null) {
5372
+ if (!isCreateRefCall(member.value, comp)) errs.push({
5373
+ ...pos,
5374
+ message: `E-REF-TYPE: ref 绑定的 '${exprSource}' 初始化器不是 createRef() 调用,运行时将报「Ref must be a RefObject」`
5375
+ });
5376
+ }
5362
5377
  }
5363
5378
  }
5364
5379
  }
@@ -8444,55 +8459,28 @@ function compileFile(code, id, options = {}) {
8444
8459
  additions
8445
8460
  };
8446
8461
  }
8447
-
8448
- //#endregion
8449
- //#region src/vite.ts
8450
- const TAG_RE = /@tag\(\s*['"]([^'"]+)['"]/g;
8451
8462
  /**
8452
- * 用于 transform 缓存键的按文件指纹(见 tagFingerprint)。
8453
- */
8454
- const FILE_HYPHEN_TAG_RE = /<([a-z][a-z0-9]*(?:-[a-z0-9]+)+)/g;
8455
- /**
8456
- * 按文件标签指纹(替代全局 `knownTags.size`)。
8463
+ * 编译**已确定要编译**的源码,跳过 `include` 判定。
8464
+ *
8465
+ * 与 {@link compileFile} 的分工:
8466
+ * - `compileFile` 面向「磁盘上可能是组件、也可能不是」的输入,先按扩展名 +
8467
+ * 特征做 include 判定;
8468
+ * - `compileSource` 面向「调用方已经决定要编译」的输入 —— 典型是 SFC 插件把
8469
+ * `.ce` 分段拼成的虚拟 TypeScript 模块。虚拟模块的 id 仍是 `.ce`(为了
8470
+ * sourcemap 指向真实文件),天然过不了 `defaultInclude` 的扩展名检查,
8471
+ * 以前只能传 `include: () => true` 绕过整个判定(连 node_modules 排除也一起绕过)。
8472
+ *
8473
+ * `options.include` 在这里被忽略。
8457
8474
  */
8458
- function tagFingerprint(usedTags, knownTags) {
8459
- if (usedTags.size === 0) return "";
8460
- const inter = [];
8461
- for (const t of usedTags) if (knownTags.has(t)) inter.push(t);
8462
- if (inter.length === 0) return "";
8463
- inter.sort();
8464
- return hash32(inter.join("\n"));
8465
- }
8466
- const SKIP_DIRS = new Set([
8467
- "node_modules",
8468
- "dist",
8469
- "build",
8470
- "out",
8471
- "coverage"
8472
- ]);
8473
- /** 递归收集 root 下 .ts/.tsx 的 @tag 名(跨文件标签注册表,TEMPLATE-CODEGEN.md §4)。 */
8474
- function scanKnownTags(root, out, depth = 0) {
8475
- if (depth > 20) return;
8476
- let entries;
8477
- try {
8478
- entries = readdirSync(root, { withFileTypes: true });
8479
- } catch {
8480
- return;
8481
- }
8482
- for (const e of entries) {
8483
- if (e.name.startsWith(".")) continue;
8484
- if (e.isDirectory()) {
8485
- if (SKIP_DIRS.has(e.name)) continue;
8486
- scanKnownTags(join(root, e.name), out, depth + 1);
8487
- } else if (/\.tsx?$/.test(e.name) && !e.name.endsWith(".d.ts")) try {
8488
- const code = readFileSync(join(root, e.name), "utf-8");
8489
- if (code.indexOf("@tag") < 0 && code.indexOf("CompElem") < 0) continue;
8490
- TAG_RE.lastIndex = 0;
8491
- let m;
8492
- while (m = TAG_RE.exec(code)) out.add(m[1].toLowerCase());
8493
- } catch {}
8494
- }
8475
+ function compileSource(code, id, options = {}) {
8476
+ return compileFile(code, id, {
8477
+ ...options,
8478
+ include: () => true
8479
+ });
8495
8480
  }
8481
+
8482
+ //#endregion
8483
+ //#region src/diagnostics.ts
8496
8484
  /** 惰性构建换行偏移索引(升序;仅在有诊断时付费一次)。 */
8497
8485
  function newlineIndex(code) {
8498
8486
  const arr = [];
@@ -8519,13 +8507,22 @@ function offsetToLineCol(code, offset, nl) {
8519
8507
  const lastNl = lo > 0 ? nl[lo - 1] : -1;
8520
8508
  return `${lo + 1}:${offset - lastNl}`;
8521
8509
  }
8510
+ /** 相对 root 的展示路径(统一正斜杠,便于点击跳转)。 */
8511
+ function relativeToRoot(id, root) {
8512
+ const norm = (s) => s.replace(/\\/g, "/");
8513
+ const rel = norm(id);
8514
+ const base = norm(root);
8515
+ return rel.startsWith(base) ? rel.slice(base.length + 1) : id;
8516
+ }
8522
8517
  /**
8523
- * 从 diagnostics 收集**约定诊断**为可上报文本,按 severity 分为 errs(E-*)与 warns(W-*)
8518
+ * 从 diagnostics 收集**约定诊断**为可上报文本,按 severity 分为 errs(E-*)与 warns(W-*)。
8519
+ *
8520
+ * `root` 只用于把绝对路径压成相对路径,不影响判定。
8524
8521
  */
8525
- function collectErrors(res, code, id, root) {
8522
+ function collectDiagnostics(res, code, id, root) {
8526
8523
  const errs = [];
8527
8524
  const warns = [];
8528
- const rel = id.startsWith(root) ? id.replace(/\\/g, "/").slice(root.replace(/\\/g, "/").length + 1) : id;
8525
+ const rel = relativeToRoot(id, root);
8529
8526
  let nl;
8530
8527
  for (const d of res.diagnostics) {
8531
8528
  const list = d.conventionErrors?.map((e) => ({
@@ -8544,8 +8541,13 @@ function collectErrors(res, code, id, root) {
8544
8541
  warns
8545
8542
  };
8546
8543
  }
8547
- /** 按 mode 上报违规(error 阻断构建 / warn 控制台 / silent 忽略)。 */
8548
- function reportErrors(msgs, mode, head, thisArg) {
8544
+ /**
8545
+ * 按 mode 上报违规:error 走插件上下文的 `this.error`(阻断构建),warn 走控制台。
8546
+ *
8547
+ * `thisArg` 是 Rollup/Vite 的插件上下文;纯 API 调用时传 undefined 也能工作
8548
+ * (退化为 `throw`)。
8549
+ */
8550
+ function reportDiagnostics(msgs, mode, head, thisArg) {
8549
8551
  if (mode === "silent" || !msgs.length) return;
8550
8552
  const text = head + "\n " + msgs.join("\n ");
8551
8553
  if (mode === "error") {
@@ -8554,6 +8556,86 @@ function reportErrors(msgs, mode, head, thisArg) {
8554
8556
  else throw new Error(text);
8555
8557
  } else console.warn(text);
8556
8558
  }
8559
+ /**
8560
+ * 一站式上报:W 码恒为控制台提示(不随档位阻断),E 码按 mode 处理。
8561
+ *
8562
+ * 返回是否发生了阻断(仅用于调用方决定是否继续)。
8563
+ */
8564
+ function reportConventionDiagnostics(collected, mode, thisArg, tag = "[compelem/compiler]") {
8565
+ if (mode === "silent") return;
8566
+ const { errs, warns } = collected;
8567
+ if (warns.length) console.warn(`${tag} ${warns.length} 条提示:\n ` + warns.join("\n "));
8568
+ reportDiagnostics(errs, mode, `${tag} 检测到 ${errs.length} 处约定错误:`, thisArg);
8569
+ }
8570
+
8571
+ //#endregion
8572
+ //#region src/tag-registry.ts
8573
+ /** `@tag('x-foo')` / `@tag(\`x-foo\`)`;一次扫描内复用,调用方负责重置 lastIndex。 */
8574
+ const TAG_RE = /@tag\s*\(\s*['"`]([^'"`]+)['"`]/g;
8575
+ /** 默认只把 `.ts` / `.tsx` 当作组件来源。 */
8576
+ const DEFAULT_TAG_EXT = /\.tsx?$/;
8577
+ const SKIP_DIRS = new Set([
8578
+ "node_modules",
8579
+ "dist",
8580
+ "build",
8581
+ "out",
8582
+ "coverage"
8583
+ ]);
8584
+ /**
8585
+ * 递归收集 root 下匹配 `extensions` 的文件里的 `@tag` 名,写入 `out`。
8586
+ *
8587
+ * 读失败的文件直接跳过:注册表是 best-effort 的,它只会**放宽**校验,
8588
+ * 不该让一次权限错误打断构建。`node_modules` 等目录不参与扫描。
8589
+ */
8590
+ function scanKnownTags(root, out, options = {}) {
8591
+ const { extensions = DEFAULT_TAG_EXT, maxDepth = 20 } = options;
8592
+ scanDir(root, out, extensions, 0, maxDepth);
8593
+ }
8594
+ function scanDir(root, out, ext, depth, maxDepth) {
8595
+ if (depth > maxDepth) return;
8596
+ let entries;
8597
+ try {
8598
+ entries = readdirSync(root, { withFileTypes: true });
8599
+ } catch {
8600
+ return;
8601
+ }
8602
+ for (const e of entries) {
8603
+ if (e.name.startsWith(".")) continue;
8604
+ if (e.isDirectory()) {
8605
+ if (SKIP_DIRS.has(e.name)) continue;
8606
+ scanDir(join(root, e.name), out, ext, depth + 1, maxDepth);
8607
+ } else if (ext.test(e.name) && !e.name.endsWith(".d.ts")) {
8608
+ let code;
8609
+ try {
8610
+ code = readFileSync(join(root, e.name), "utf-8");
8611
+ } catch {
8612
+ continue;
8613
+ }
8614
+ if (code.indexOf("@tag") < 0 && code.indexOf("CompElem") < 0) continue;
8615
+ TAG_RE.lastIndex = 0;
8616
+ let m;
8617
+ while (m = TAG_RE.exec(code)) out.add(m[1].toLowerCase());
8618
+ }
8619
+ }
8620
+ }
8621
+
8622
+ //#endregion
8623
+ //#region src/vite.ts
8624
+ /**
8625
+ * 用于 transform 缓存键的按文件指纹(见 tagFingerprint)。
8626
+ */
8627
+ const FILE_HYPHEN_TAG_RE = /<([a-z][a-z0-9]*(?:-[a-z0-9]+)+)/g;
8628
+ /**
8629
+ * 按文件标签指纹(替代全局 `knownTags.size`)。
8630
+ */
8631
+ function tagFingerprint(usedTags, knownTags) {
8632
+ if (usedTags.size === 0) return "";
8633
+ const inter = [];
8634
+ for (const t of usedTags) if (knownTags.has(t)) inter.push(t);
8635
+ if (inter.length === 0) return "";
8636
+ inter.sort();
8637
+ return hash32(inter.join("\n"));
8638
+ }
8557
8639
  /** compelem 编译期优化插件。 */
8558
8640
  function compelemCompiler(options = {}) {
8559
8641
  let root = process.cwd();
@@ -8607,11 +8689,7 @@ function compelemCompiler(options = {}) {
8607
8689
  }
8608
8690
  transformCache.set(cacheKey, res);
8609
8691
  }
8610
- if (convMode !== "silent") {
8611
- const { errs, warns } = collectErrors(res, code, id, root);
8612
- if (warns.length) console.warn(`[compelem/compiler] ${warns.length} 条提示:\n ` + warns.join("\n "));
8613
- reportErrors(errs, convMode, `[compelem/compiler] 检测到 ${errs.length} 处约定错误:`, this);
8614
- }
8692
+ if (convMode !== "silent") reportConventionDiagnostics(collectDiagnostics(res, code, id, root), convMode, this);
8615
8693
  if (!res.changed) return null;
8616
8694
  if (options.emitJs) emitCompiledJs(id, res.code, root);
8617
8695
  if (options.verbose) {
@@ -8646,5 +8724,5 @@ function emitCompiledJs(id, code, root) {
8646
8724
  var vite_default = compelemCompiler;
8647
8725
 
8648
8726
  //#endregion
8649
- export { buildStaticLiteral as a, StaticAnalysisError as c, analyzeRender as d, analyzeFile as f, parse$1 as h, defaultInclude as i, assertStaticAnalysis as l, isCompelemSource as m, vite_default as n, injectInto as o, collectCompelemImports as p, compileFile as r, DEBUG_DIV as s, compelemCompiler as t, inlineTemplateMethods as u };
8650
- //# sourceMappingURL=vite-CY_6E-Ut.js.map
8727
+ export { collectCompelemImports as C, analyzeFile as S, parse$1 as T, DEBUG_DIV as _, scanKnownTags as a, inlineTemplateMethods as b, offsetToLineCol as c, reportDiagnostics as d, compileFile as f, injectInto as g, buildStaticLiteral as h, TAG_RE as i, relativeToRoot as l, defaultInclude as m, vite_default as n, collectDiagnostics as o, compileSource as p, DEFAULT_TAG_EXT as r, newlineIndex as s, compelemCompiler as t, reportConventionDiagnostics as u, StaticAnalysisError as v, isCompelemSource as w, analyzeRender as x, assertStaticAnalysis as y };
8728
+ //# sourceMappingURL=vite-CLK9td6W.js.map