@linkdesk/plugin-sdk 0.1.31 → 0.1.33

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.
Files changed (70) hide show
  1. package/dist/eslint/checks/appearance-ownership.d.ts +91 -0
  2. package/dist/eslint/checks/appearance-ownership.d.ts.map +1 -0
  3. package/dist/eslint/checks/appearance-ownership.js +314 -0
  4. package/dist/eslint/checks/appearance-ownership.js.map +1 -0
  5. package/dist/eslint/checks/appearance-ownership.test.d.ts +2 -0
  6. package/dist/eslint/checks/appearance-ownership.test.d.ts.map +1 -0
  7. package/dist/eslint/checks/appearance-ownership.test.js +348 -0
  8. package/dist/eslint/checks/appearance-ownership.test.js.map +1 -0
  9. package/dist/eslint/checks/command-ownership.d.ts +33 -4
  10. package/dist/eslint/checks/command-ownership.d.ts.map +1 -1
  11. package/dist/eslint/checks/command-ownership.js +22 -2
  12. package/dist/eslint/checks/command-ownership.js.map +1 -1
  13. package/dist/eslint/checks/command-ownership.test.d.ts +2 -0
  14. package/dist/eslint/checks/command-ownership.test.d.ts.map +1 -0
  15. package/dist/eslint/checks/command-ownership.test.js +207 -0
  16. package/dist/eslint/checks/command-ownership.test.js.map +1 -0
  17. package/dist/eslint/checks/config-ownership.test.d.ts +2 -0
  18. package/dist/eslint/checks/config-ownership.test.d.ts.map +1 -0
  19. package/dist/eslint/checks/config-ownership.test.js +252 -0
  20. package/dist/eslint/checks/config-ownership.test.js.map +1 -0
  21. package/dist/eslint/checks/context-ownership.d.ts +74 -0
  22. package/dist/eslint/checks/context-ownership.d.ts.map +1 -0
  23. package/dist/eslint/checks/context-ownership.js +173 -0
  24. package/dist/eslint/checks/context-ownership.js.map +1 -0
  25. package/dist/eslint/checks/context-ownership.test.d.ts +2 -0
  26. package/dist/eslint/checks/context-ownership.test.d.ts.map +1 -0
  27. package/dist/eslint/checks/context-ownership.test.js +205 -0
  28. package/dist/eslint/checks/context-ownership.test.js.map +1 -0
  29. package/dist/eslint/checks/disable.d.ts +10 -0
  30. package/dist/eslint/checks/disable.d.ts.map +1 -1
  31. package/dist/eslint/checks/disable.js +10 -0
  32. package/dist/eslint/checks/disable.js.map +1 -1
  33. package/dist/eslint/checks/plugin-prefix.test.d.ts +2 -0
  34. package/dist/eslint/checks/plugin-prefix.test.d.ts.map +1 -0
  35. package/dist/eslint/checks/plugin-prefix.test.js +229 -0
  36. package/dist/eslint/checks/plugin-prefix.test.js.map +1 -0
  37. package/dist/eslint/checks/reserved-classes.test.d.ts +2 -0
  38. package/dist/eslint/checks/reserved-classes.test.d.ts.map +1 -0
  39. package/dist/eslint/checks/reserved-classes.test.js +122 -0
  40. package/dist/eslint/checks/reserved-classes.test.js.map +1 -0
  41. package/dist/eslint/checks/selector-form.test.d.ts +2 -0
  42. package/dist/eslint/checks/selector-form.test.d.ts.map +1 -0
  43. package/dist/eslint/checks/selector-form.test.js +211 -0
  44. package/dist/eslint/checks/selector-form.test.js.map +1 -0
  45. package/dist/eslint/checks/token-scope.test.d.ts +2 -0
  46. package/dist/eslint/checks/token-scope.test.d.ts.map +1 -0
  47. package/dist/eslint/checks/token-scope.test.js +185 -0
  48. package/dist/eslint/checks/token-scope.test.js.map +1 -0
  49. package/dist/eslint/index.d.ts +26 -3
  50. package/dist/eslint/index.d.ts.map +1 -1
  51. package/dist/eslint/index.js +24 -3
  52. package/dist/eslint/index.js.map +1 -1
  53. package/dist/eslint/lint.d.ts +39 -0
  54. package/dist/eslint/lint.d.ts.map +1 -1
  55. package/dist/eslint/lint.js +113 -3
  56. package/dist/eslint/lint.js.map +1 -1
  57. package/dist/pack.test.d.ts +2 -0
  58. package/dist/pack.test.d.ts.map +1 -0
  59. package/dist/pack.test.js +58 -0
  60. package/dist/pack.test.js.map +1 -0
  61. package/dist/publish.test.d.ts +2 -0
  62. package/dist/publish.test.d.ts.map +1 -0
  63. package/dist/publish.test.js +340 -0
  64. package/dist/publish.test.js.map +1 -0
  65. package/dist/validate.test.d.ts +2 -0
  66. package/dist/validate.test.d.ts.map +1 -0
  67. package/dist/validate.test.js +107 -0
  68. package/dist/validate.test.js.map +1 -0
  69. package/package.json +68 -68
  70. package/schemas/host-reserved.json +22 -6
@@ -0,0 +1,229 @@
1
+ /**
2
+ * check-css-namespace 腿·**本仓前缀判据**(E6#109h-b①)单测——插件 CSS 里给自有元素起的类名、以及
3
+ * 自己的 `@keyframes` 名,必须以本仓 `pluginId` 加一个连字符开头。
4
+ *
5
+ * 为什么值得一条测试:这条腿的**两条失效方向都是静默的**——
6
+ * · 太松(放过一个裸名)⇒ 插件 ↔ 插件轴继续无保护,表现是「不报错、只是长得不对」(`.badge` 案同形);
7
+ * · 太紧(误伤合法消费)⇒ 插件仓 CI 白红,作者学会「看到红就 disable」,门禁从此失效。
8
+ * 故四条判据(①类名 ②关键帧 ③`ldk-` 边界 ④fail-closed)**正反两向都钉住**,另加两条元判据:
9
+ * · **同一份 CSS + 不同 pluginId ⇒ 不同裁决**(证明这条腿真的在读 manifest,不是恒红/恒绿);
10
+ * · **豁免注释只盖 ①②,盖不住 ③④**(身份/清单问题不受样式表注释管辖)。
11
+ */
12
+ import { describe, expect, it } from "vitest";
13
+ import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
14
+ import { tmpdir } from "node:os";
15
+ import { basename, join } from "node:path";
16
+ import { runPluginPrefixCheck, resolvePluginIdForCss } from "./plugin-prefix.js";
17
+ /** 保留名夹具(不依赖真实清单内容——真实清单会被后续轮次改动);⚠️ 只剩 `keyframes`(`classes` 已随判据① 退役) */
18
+ const RESERVED = {
19
+ keyframes: [{ name: "drop-zone-in", why: "宿主拖放区入场动画" }],
20
+ };
21
+ /**
22
+ * 造一个临时插件工程。
23
+ * `manifest` 传对象 ⇒ 写成 JSONC(含注释与尾逗号,钉住「manifest 是 JSONC」这条);传 null ⇒ 不写 plugin.json。
24
+ */
25
+ function withPlugin(opts, fn) {
26
+ const base = mkdtempSync(join(tmpdir(), "css-prefix-"));
27
+ const root = opts.dirName ? join(base, opts.dirName) : base;
28
+ mkdirSync(join(root, "src", "styles"), { recursive: true });
29
+ if (opts.manifest !== null) {
30
+ // 🔴 写成**带注释与尾逗号**的 JSONC——作者面允许这么写,读 manifest 的代码必须容忍
31
+ const raw = opts.manifest ?? { pluginId: "file-tree", name: "文件树", version: "1.0.0" };
32
+ writeFileSync(join(root, "plugin.json"), `{\n // 作者允许的注释\n ${JSON.stringify(raw).slice(1, -1)},\n}\n`, "utf8");
33
+ }
34
+ if (opts.css !== undefined) {
35
+ writeFileSync(join(root, opts.cssRel ?? join("src", "styles", "App.css")), opts.css, "utf8");
36
+ }
37
+ try {
38
+ fn(root);
39
+ }
40
+ finally {
41
+ rmSync(base, { recursive: true, force: true });
42
+ }
43
+ }
44
+ describe("判据① 裸定义类名必须带本仓前缀", () => {
45
+ it("正控:带本仓前缀 / scoped 调优 / 复合状态类 ⇒ 零违规", () => {
46
+ withPlugin({
47
+ css: ".file-tree-card { color: red; }\n" + // 带前缀
48
+ ".control-bar .file-tree-thing { margin: 0; }\n" + // scoped 调优(无祖先才叫裸定义)
49
+ ".file-tree-card.active { border: 0; }\n", // 复合状态类不占名
50
+ }, (root) => {
51
+ const r = runPluginPrefixCheck(root, RESERVED);
52
+ expect(r.violations).toHaveLength(0);
53
+ expect(r.pluginId).toBe("file-tree");
54
+ expect(r.pluginIdSource).toBe("plugin.json");
55
+ });
56
+ });
57
+ it("负控:新裸名 ⇒ 红,且报点含 文件 + 行 + 名字 + 应改成什么", () => {
58
+ withPlugin({ css: ".file-tree-ok { color: red; }\n.notmine-panel { color: blue; }\n" }, (root) => {
59
+ const r = runPluginPrefixCheck(root, RESERVED);
60
+ expect(r.violations).toHaveLength(1);
61
+ const v = r.violations[0];
62
+ expect(v.file).toBe("src/styles/App.css");
63
+ expect(v.line).toBe(2);
64
+ expect(v.message).toContain(".notmine-panel");
65
+ expect(v.message).toContain('应以 "file-tree-" 开头');
66
+ expect(r.classes).toEqual([
67
+ expect.objectContaining({ name: "notmine-panel", suggested: "file-tree-notmine-panel", reserved: false }),
68
+ ]);
69
+ });
70
+ });
71
+ it("负控:反向改名成 <pluginId>- 前缀 ⇒ 该点消失(不是「恒红」)", () => {
72
+ withPlugin({ css: ".file-tree-notmine-panel { color: blue; }\n" }, (root) => {
73
+ expect(runPluginPrefixCheck(root, RESERVED).violations).toHaveLength(0);
74
+ });
75
+ });
76
+ it("🔴 分界:带连字符但不属于本仓也必红(`.toolbar-bar`)——规则不是「只要含连字符」", () => {
77
+ withPlugin({ css: ".toolbar-bar { color: red; }\n" }, (root) => {
78
+ const r = runPluginPrefixCheck(root, RESERVED);
79
+ expect(r.violations).toHaveLength(1);
80
+ expect(r.violations[0].message).toContain(".toolbar-bar");
81
+ });
82
+ });
83
+ it("🔴 退役记录(E6#109p-b · 1.28):`.badge` 只由本腿那条**结构性**判据报,不再补「宿主保留名」措辞", () => {
84
+ withPlugin({ css: ".badge { background: var(--accent); }\n" }, (root) => {
85
+ const r = runPluginPrefixCheck(root, RESERVED);
86
+ expect(r.violations).toHaveLength(1); // 照旧只报一次(判据① 报点本就由本腿覆盖)
87
+ // 判据①(裸定义宿主保留类名)已退役 ⇒ 类名侧的保留名补充措辞随之删除:
88
+ // 「.badge 是保留名」这件事今天只由**本腿的前缀规则**兜(裸名必须以 <pluginId>- 开头)。
89
+ expect(r.violations[0].message).not.toContain("宿主保留名");
90
+ expect(r.classes[0].reserved).toBe(false);
91
+ });
92
+ });
93
+ it("多文件:各自出点、行号按本文件算", () => {
94
+ withPlugin({ css: ".notmine-a { color: red; }\n" }, (root) => {
95
+ writeFileSync(join(root, "src", "styles", "Other.css"), "\n\n.notmine-b { color: blue; }\n", "utf8");
96
+ const r = runPluginPrefixCheck(root, RESERVED);
97
+ expect(r.violations.map((v) => `${v.file}:${v.line}`)).toEqual(["src/styles/App.css:1", "src/styles/Other.css:3"]);
98
+ });
99
+ });
100
+ });
101
+ describe("判据② 关键帧名必须带本仓前缀", () => {
102
+ it("正控:带前缀的关键帧 ⇒ 零违规", () => {
103
+ withPlugin({ css: "@keyframes file-tree-fadeIn { from { opacity: 0 } }\n" }, (root) => {
104
+ expect(runPluginPrefixCheck(root, RESERVED).violations).toHaveLength(0);
105
+ });
106
+ });
107
+ it("负控:无前缀关键帧 ⇒ 红,且提醒同笔改 animation: 引用", () => {
108
+ withPlugin({ css: "@keyframes fadeIn { from { opacity: 0 } }\n" }, (root) => {
109
+ const r = runPluginPrefixCheck(root, RESERVED);
110
+ expect(r.violations).toHaveLength(1);
111
+ expect(r.violations[0].message).toContain("@keyframes fadeIn");
112
+ expect(r.violations[0].message).toContain("animation: 引用处");
113
+ expect(r.keyframes[0]).toEqual(expect.objectContaining({ name: "fadeIn", suggested: "file-tree-fadeIn", line: 1 }));
114
+ });
115
+ });
116
+ it("负控:撞宿主登记关键帧名 ⇒ 红 + 措辞里点出「与宿主关键帧同名」", () => {
117
+ withPlugin({ css: "@keyframes drop-zone-in { from { opacity: 0 } }\n" }, (root) => {
118
+ const r = runPluginPrefixCheck(root, RESERVED);
119
+ expect(r.violations).toHaveLength(1);
120
+ expect(r.violations[0].message).toContain("宿主关键帧");
121
+ expect(r.keyframes[0].reserved).toBe(true);
122
+ });
123
+ });
124
+ });
125
+ describe("判据③ pluginId 不得以 ldk- 开头", () => {
126
+ it("负控:pluginId: \"ldk-tools\" ⇒ 红(借宿主命名空间)", () => {
127
+ withPlugin({ manifest: { pluginId: "ldk-tools" }, css: ".ldk-tools-panel { color: red; }\n" }, (root) => {
128
+ const r = runPluginPrefixCheck(root, RESERVED);
129
+ expect(r.boundary).toBe("ldk-tools");
130
+ expect(r.violations).toHaveLength(1); // 类名本身合规 ⇒ 只有边界这一条
131
+ expect(r.violations[0].message).toContain("ldk-");
132
+ expect(r.classes).toHaveLength(0);
133
+ });
134
+ });
135
+ it("正控:`ldk` 前缀但不是 `ldk-`(如 `ldkfoo`)⇒ 不误伤", () => {
136
+ withPlugin({ manifest: { pluginId: "ldkfoo" }, css: ".ldkfoo-panel { color: red; }\n" }, (root) => {
137
+ expect(runPluginPrefixCheck(root, RESERVED).violations).toHaveLength(0);
138
+ });
139
+ });
140
+ it("🔴 目录名兜底同样受管辖:无 pluginId + 目录名 `ldk-*` ⇒ 仍红(不是「声明了才管」)", () => {
141
+ // 实测发现:本单测的临时目录前缀原本叫 `ldk-prefix-*`,于是**判据④的兜底路径**直接把 ③ 撞出来了
142
+ // ——这条判据连「没声明 pluginId、按目录名兜底」的那条路也管得住,故留成常驻用例。
143
+ withPlugin({ manifest: { name: "x" }, dirName: "ldk-tools" }, (root) => {
144
+ const r = runPluginPrefixCheck(root, RESERVED);
145
+ expect(r.pluginId).toBe("ldk-tools");
146
+ expect(r.pluginIdSource).toBe("dir-name");
147
+ expect(r.boundary).toBe("ldk-tools");
148
+ expect(r.violations).toHaveLength(1);
149
+ });
150
+ });
151
+ });
152
+ describe("判据④ fail-closed(拿不到前缀 ⇒ 红,不许静默放过)", () => {
153
+ it("负控:plugin.json 不存在 ⇒ 红(单点、指名 plugin.json)", () => {
154
+ withPlugin({ manifest: null, css: ".anything { color: red; }\n" }, (root) => {
155
+ const r = runPluginPrefixCheck(root, RESERVED);
156
+ expect(r.error).toContain("plugin.json 不在工程根");
157
+ expect(r.violations).toHaveLength(1);
158
+ expect(r.violations[0].file).toBe("plugin.json");
159
+ expect(r.classes).toHaveLength(0); // 判不了 ⇒ 不假装判过
160
+ });
161
+ });
162
+ it("负控:plugin.json 语法错 ⇒ 红(报点带上 jsonc 的解析错误)", () => {
163
+ withPlugin({ manifest: null }, (root) => {
164
+ writeFileSync(join(root, "plugin.json"), "{ this is not json ", "utf8");
165
+ const r = runPluginPrefixCheck(root, RESERVED);
166
+ expect(r.error).toContain("plugin.json 读不出");
167
+ expect(r.violations).toHaveLength(1);
168
+ });
169
+ });
170
+ it("负控:无 pluginId 且目录名也不能兜底 ⇒ 红", () => {
171
+ withPlugin({ manifest: { name: "x" }, css: ".x { color: red; }\n", dirName: "bad dir name" }, (root) => {
172
+ const r = runPluginPrefixCheck(root, RESERVED);
173
+ expect(r.error).toContain("也不能兜底");
174
+ expect(r.violations).toHaveLength(1);
175
+ });
176
+ });
177
+ it("正控:无 pluginId 但目录名可用 ⇒ 绿 + 打印「用了目录名」那一行(不静默)", () => {
178
+ withPlugin({ manifest: { name: "x", version: "1.0.0" } }, (root) => {
179
+ const cur = resolvePluginIdForCss(root);
180
+ expect(cur.error).toBeNull();
181
+ expect(cur.source).toBe("dir-name");
182
+ expect(cur.pluginId).toBe(basename(root));
183
+ expect(cur.note).toContain("兜底");
184
+ // 前缀按目录名 ⇒ 用目录名带前缀的类名应当绿
185
+ writeFileSync(join(root, "src", "styles", "App.css"), `.${cur.pluginId}-panel { color: red; }\n`, "utf8");
186
+ const r = runPluginPrefixCheck(root, RESERVED);
187
+ expect(r.violations).toHaveLength(0);
188
+ expect(r.pluginIdNote).toContain("兜底");
189
+ });
190
+ });
191
+ });
192
+ describe("元判据(判据自身的正/负控)", () => {
193
+ it("🔴 同一份 CSS + 两个不同 pluginId ⇒ 裁决不同(证明真在读 manifest,不是恒红/恒绿)", () => {
194
+ const css = ".shared-name { color: red; }\n";
195
+ withPlugin({ manifest: { pluginId: "alpha" }, css }, (rootA) => {
196
+ withPlugin({ manifest: { pluginId: "beta" }, css }, (rootB) => {
197
+ const a = runPluginPrefixCheck(rootA, RESERVED);
198
+ const b = runPluginPrefixCheck(rootB, RESERVED);
199
+ expect(a.violations).toHaveLength(1); // `.shared-name` 不带 `alpha-`
200
+ expect(a.classes[0].suggested).toBe("alpha-shared-name");
201
+ expect(b.violations).toHaveLength(1); // 也不带 `beta-`
202
+ expect(b.classes[0].suggested).toBe("beta-shared-name");
203
+ // 反向:各自带自己的前缀 ⇒ 都绿(同一份 CSS 换个 pluginId 就换了裁决)
204
+ withPlugin({ manifest: { pluginId: "alpha" }, css: ".alpha-shared-name { color: red; }\n" }, (r2) => {
205
+ withPlugin({ manifest: { pluginId: "beta" }, css: ".alpha-shared-name { color: red; }\n" }, (r3) => {
206
+ expect(runPluginPrefixCheck(r2, RESERVED).violations).toHaveLength(0);
207
+ expect(runPluginPrefixCheck(r3, RESERVED).violations).toHaveLength(1); // 对 beta 而言这是外来的名字
208
+ });
209
+ });
210
+ });
211
+ });
212
+ });
213
+ it("豁免注释只盖 ①②(样式选择),盖不住 ③④(身份/清单)", () => {
214
+ // ① 被文件级 disable 盖住
215
+ withPlugin({ css: "/* eslint-disable linkdesk/no-reserved-class-name -- 知情绕行 */\n.notmine-panel { color: red; }\n" }, (root) => {
216
+ const r = runPluginPrefixCheck(root, RESERVED);
217
+ expect(r.violations).toHaveLength(0);
218
+ expect(r.classes).toHaveLength(0); // 清单里也不出现(与腿同口径)
219
+ });
220
+ // ④ 盖不住:plugin.json 缺失 = 工程根问题,样式表里写什么注释都没用
221
+ withPlugin({ manifest: null }, (root) => {
222
+ writeFileSync(join(root, "src", "styles", "App.css"), "/* eslint-disable linkdesk/no-reserved-class-name -- 想绕过去 */\n.x { color: red; }\n", "utf8");
223
+ const r = runPluginPrefixCheck(root, RESERVED);
224
+ expect(r.violations).toHaveLength(1);
225
+ expect(r.violations[0].file).toBe("plugin.json");
226
+ });
227
+ });
228
+ });
229
+ //# sourceMappingURL=plugin-prefix.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-prefix.test.js","sourceRoot":"","sources":["../../../src/eslint/checks/plugin-prefix.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=reserved-classes.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reserved-classes.test.d.ts","sourceRoot":"","sources":["../../../src/eslint/checks/reserved-classes.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,122 @@
1
+ /**
2
+ * check-css-namespace 腿 单测——**@keyframes 名不得撞宿主保留关键帧名**。
3
+ *
4
+ * 为什么值得一条测试:这条腿的错法同样是**静默**的——关键帧撞名在界面上只表现成「动画不对」,
5
+ * 不报错;而不查/误报又会让插件仓 CI 白红。故正反两向都钉住。
6
+ *
7
+ * ── 🔴 判据①「裸定义宿主保留类名」已退役(E6#109p-b · 轮次 1.28 · 2026-09-16)──
8
+ * 退役记录写在这里(连同一条**退役钉子**测试:留 `classes` 字段也不再生效):
9
+ * 1.27 全量门禁体检实测(`docs/02-Electron架构/E6_插件生态与发布/01-插件独立构建/样式命名空间归一化/27-任务-门禁健康度体检.md` §十二.2(a)):
10
+ * · 清单的 `classes` 段**已随 E6#109l-b(1.21b)整块删除** ⇒ `classMap` 恒空 ⇒ 判据想报也报不出来;
11
+ * · 同一轴上 `plugin-prefix.ts` 的「裸定义必须带 `<pluginId>-`」**完全覆盖**它;
12
+ * · `classes` 段是**终态删除**(类名规则已结构性化,登记表只剩 `keyframes`)⇒ 输入不会回来。
13
+ * ⇒ 代码路径 + `ReservedNames.classes` 字段一并删除。**判据② 保留**:拿不到 pluginId 时前缀腿
14
+ * fail-closed,此时本腿是唯一能逐点报出「你占了宿主关键帧名」的腿(1.27 实验 (d) 实证)。
15
+ */
16
+ import { describe, expect, it } from "vitest";
17
+ import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
18
+ import { tmpdir } from "node:os";
19
+ import { join } from "node:path";
20
+ import { runReservedClassCheck, loadReservedNames } from "./reserved-classes.js";
21
+ const RESERVED = {
22
+ keyframes: [{ name: "ldk-selectbox-in", why: "共享组件下拉入场" }],
23
+ };
24
+ function withFixture(css, fn) {
25
+ const root = mkdtempSync(join(tmpdir(), "ldk-reserved-"));
26
+ mkdirSync(join(root, "src"), { recursive: true });
27
+ writeFileSync(join(root, "src", "style.css"), css, "utf8");
28
+ try {
29
+ fn(root);
30
+ }
31
+ finally {
32
+ rmSync(root, { recursive: true, force: true });
33
+ }
34
+ }
35
+ describe("runReservedClassCheck —— 正控(不许误伤)", () => {
36
+ it("带插件前缀的类名 ⇒ 零违规", () => {
37
+ withFixture(".my-plugin-card { color: red; }\n.my-plugin-title { color: blue; }\n", (root) => {
38
+ expect(runReservedClassCheck(root, RESERVED)).toHaveLength(0);
39
+ });
40
+ });
41
+ it("scoped 调优(.my-bar .badge)⇒ 零违规", () => {
42
+ withFixture(".my-bar .badge { margin-left: 4px; }\n", (root) => {
43
+ expect(runReservedClassCheck(root, RESERVED)).toHaveLength(0);
44
+ });
45
+ });
46
+ it("复合状态类不占名(.my-card.active)⇒ 零违规", () => {
47
+ withFixture(".my-card.active { border-color: var(--accent); }\n", (root) => {
48
+ expect(runReservedClassCheck(root, RESERVED)).toHaveLength(0);
49
+ });
50
+ });
51
+ it("带前缀的关键帧(本仓自己的)⇒ 零违规", () => {
52
+ withFixture("@keyframes probe-demo-fadeIn { from { opacity: 0 } }\n", (root) => {
53
+ expect(runReservedClassCheck(root, RESERVED)).toHaveLength(0);
54
+ });
55
+ });
56
+ it("保留名清单为空 ⇒ 不产生任何违规(文件缺失时不假装有判据)", () => {
57
+ withFixture("@keyframes ldk-selectbox-in { from { opacity: 0 } }\n", (root) => {
58
+ expect(runReservedClassCheck(root, { keyframes: [] })).toHaveLength(0);
59
+ });
60
+ });
61
+ });
62
+ describe("runReservedClassCheck —— 负控(必红)", () => {
63
+ it("@keyframes 撞宿主关键帧名 ⇒ 红,且报出文件名与名字", () => {
64
+ withFixture("@keyframes ldk-selectbox-in { from { opacity: 0 } }\n", (root) => {
65
+ const v = runReservedClassCheck(root, RESERVED);
66
+ expect(v).toHaveLength(1);
67
+ expect(v[0].file).toBe("src/style.css");
68
+ expect(v[0].message).toContain("ldk-selectbox-in");
69
+ expect(v[0].message).toContain("与宿主关键帧同名");
70
+ });
71
+ });
72
+ it("注释里的关键帧名不算违规(判据只看真实规则)", () => {
73
+ withFixture("/* 注意:别用 @keyframes ldk-selectbox-in */\n.my-card { color: red; }\n", (root) => {
74
+ expect(runReservedClassCheck(root, RESERVED)).toHaveLength(0);
75
+ });
76
+ });
77
+ });
78
+ describe("🔴 判据① 退役钉子(E6#109p-b · 1.28)", () => {
79
+ it("裸定义 `.badge` **不再**由本腿报(退役后它只由前缀腿那条结构性判据管)", () => {
80
+ withFixture(".badge { color: red; }\n.input { padding: 2px; }\n", (root) => {
81
+ // 退役前这两行各报 1 条(夹具清单里给过 badge/input);退役后本腿一条都不报。
82
+ expect(runReservedClassCheck(root, RESERVED)).toHaveLength(0);
83
+ });
84
+ });
85
+ it("清单里就算还留着历史的 `classes` 键 ⇒ 也一律忽略(输入不会回来)", () => {
86
+ const root = mkdtempSync(join(tmpdir(), "ldk-reserved-"));
87
+ const file = join(root, "reserved-class-names.json");
88
+ writeFileSync(file, JSON.stringify({
89
+ classes: { shared: [{ name: "demo-shared", why: "夹具——历史遗留" }], host: [{ name: "demo-host", why: "夹具" }] },
90
+ keyframes: [{ name: "demo-keyframe", why: "夹具" }],
91
+ }), "utf8");
92
+ try {
93
+ const names = loadReservedNames(file);
94
+ expect("classes" in names).toBe(false); // 字段已删(不再读、不再暴露)
95
+ expect(names.keyframes.map((k) => k.name)).toEqual(["demo-keyframe"]);
96
+ mkdirSync(join(root, "src"), { recursive: true });
97
+ writeFileSync(join(root, "src", "style.css"), ".demo-shared { color: red; }\n", "utf8");
98
+ expect(runReservedClassCheck(root, names)).toHaveLength(0);
99
+ }
100
+ finally {
101
+ rmSync(root, { recursive: true, force: true });
102
+ }
103
+ });
104
+ });
105
+ describe("runReservedClassCheck —— 知情绕行", () => {
106
+ it("标准 disable 注释 ⇒ 该行豁免", () => {
107
+ withFixture("/* eslint-disable-next-line linkdesk/no-reserved-class-name -- 内容画布:随宿主动画走 */\n" +
108
+ "@keyframes ldk-selectbox-in { from { opacity: 0 } }\n", (root) => {
109
+ expect(runReservedClassCheck(root, RESERVED)).toHaveLength(0);
110
+ });
111
+ });
112
+ });
113
+ describe("loadReservedNames —— 随包清单可读", () => {
114
+ it("包内 schemas/reserved-class-names.json 能被读到(只剩 keyframes;classes 段是终态删除)", () => {
115
+ const names = loadReservedNames();
116
+ // 🔴 E6#109p-b(1.28):`classes` 整块早在 E6#109l-b 就从清单删了 ⇒ 本轮连**读它的代码**一起退役。
117
+ // 反向断言仍有意为之:谁把 `classes` 加回清单(或加回字段)⇒ 这条当场红,逼一次知情决策。
118
+ expect(names.keyframes.map((k) => k.name)).toContain("ldk-selectbox-in");
119
+ expect("classes" in names).toBe(false);
120
+ });
121
+ });
122
+ //# sourceMappingURL=reserved-classes.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reserved-classes.test.js","sourceRoot":"","sources":["../../../src/eslint/checks/reserved-classes.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=selector-form.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selector-form.test.d.ts","sourceRoot":"","sources":["../../../src/eslint/checks/selector-form.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,211 @@
1
+ /**
2
+ * check-css-namespace 腿·**选择器形态判据**(E6#109o-b · 1.26)单测。
3
+ *
4
+ * 覆盖 32 号档 §三.3 的插件侧那一表:负控①–⑦ + 正控①–⑤(**正控条数 ≥ 负控条数**的纪律下面
5
+ * 又补了三条,见文件末的「补充正控」)。
6
+ *
7
+ * 为什么值得一条测试(沿用 `token-scope.test.ts` 的理由):这条腿**两条失效方向都是静默的**——
8
+ * · 太松(放过一条 `button { }`)⇒ 它改掉宿主与其他插件**所有**按钮,表现是「某个页面某个控件
9
+ * 看着不对」,而**任何日志都不会响**(这就是本系列一句话根因:不是问题在长大,是尺子一直在漏);
10
+ * · 太紧(把 `.my-root :focus-visible { }` 判成无锚)⇒ **假红**,而假红会让真红失效
11
+ * (22 号档 §10.3):作者学会「看到红就 disable」,门禁失效。
12
+ * 🔴 这条正是 1.25 实测的 **F3**:`hasAncestor()` / `subjectOf()` 会把 `.x :pseudo` 误判成
13
+ * `.x` 的一次**顶层定义** ⇒ 本轴自带 `formOf()`,下面「正控②」就是它的反面钉子。
14
+ */
15
+ import { describe, expect, it } from "vitest";
16
+ import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
17
+ import { tmpdir } from "node:os";
18
+ import { join } from "node:path";
19
+ import { runSelectorFormCheck, judgeSelectorForm } from "./selector-form.js";
20
+ import { formOf } from "./css-selectors.js";
21
+ /** 造一个临时插件工程(`manifest` 传 null ⇒ 不写 plugin.json,用来钉 fail-closed) */
22
+ function withPlugin(opts, fn) {
23
+ const base = mkdtempSync(join(tmpdir(), "css-selector-form-"));
24
+ const root = opts.dirName ? join(base, opts.dirName) : base;
25
+ mkdirSync(join(root, "src", "styles"), { recursive: true });
26
+ if (opts.manifest !== null) {
27
+ const raw = opts.manifest ?? { pluginId: "demo-plugin", name: "夹具", version: "1.0.0" };
28
+ writeFileSync(join(root, "plugin.json"), `{\n // 注释\n ${JSON.stringify(raw).slice(1, -1)},\n}\n`, "utf8");
29
+ }
30
+ if (opts.css !== undefined) {
31
+ writeFileSync(join(root, opts.cssRel ?? join("src", "styles", "App.css")), opts.css, "utf8");
32
+ }
33
+ try {
34
+ fn(root);
35
+ }
36
+ finally {
37
+ rmSync(base, { recursive: true, force: true });
38
+ }
39
+ }
40
+ describe("判据 S2:插件禁无锚选择器(元素/通配/属性/伪类/伪元素/id 一视同仁)", () => {
41
+ it("负控①:`* { margin: 0 }` ⇒ S2 红", () => {
42
+ withPlugin({ css: "* { margin: 0; }\n" }, (root) => {
43
+ const r = runSelectorFormCheck(root);
44
+ expect(r.error).toBeNull();
45
+ expect(r.violations).toHaveLength(1);
46
+ expect(r.anchorless[0].code).toBe("S2");
47
+ expect(r.violations[0].file).toBe("src/styles/App.css");
48
+ expect(r.violations[0].line).toBe(1);
49
+ });
50
+ });
51
+ it("负控②:`@media print { button { border: none } }` ⇒ S2 红(**@media 内同样是顶层**)", () => {
52
+ withPlugin({ css: "@media print {\n button { border: none; }\n}\n" }, (root) => {
53
+ const r = runSelectorFormCheck(root);
54
+ expect(r.anchorless).toHaveLength(1);
55
+ expect(r.anchorless[0].selector).toBe("button");
56
+ expect(r.violations[0].line).toBe(2);
57
+ });
58
+ });
59
+ it("负控③:`#some-id { display: none }` ⇒ S2 红(**id 与元素同罪**)", () => {
60
+ withPlugin({ css: "#some-id { display: none; }\n" }, (root) => {
61
+ const r = runSelectorFormCheck(root);
62
+ expect(r.anchorless).toHaveLength(1);
63
+ expect(r.anchorless[0].code).toBe("S2");
64
+ expect(r.anchorless[0].form.kind).toBe("anchored"); // id 是**名字锚** ⇒ 形态上是 anchored
65
+ });
66
+ });
67
+ it("负控③b:限定形态 `html #some-id { }` 同样红(id 在复合里也算)", () => {
68
+ withPlugin({ css: "html #some-id { display: none; }\n" }, (root) => {
69
+ expect(runSelectorFormCheck(root).anchorless).toHaveLength(1);
70
+ });
71
+ });
72
+ it("负控④:`:root { --x: 1 }` ⇒ S2 红(无锚;也命中 token 腿,允许双报)", () => {
73
+ withPlugin({ css: ":root { --x: 1; }\n" }, (root) => {
74
+ const r = runSelectorFormCheck(root);
75
+ expect(r.anchorless).toHaveLength(1);
76
+ expect(r.anchorless[0].selector).toBe(":root");
77
+ });
78
+ });
79
+ it("负控④b:纯伪元素形态 `::-webkit-scrollbar { }` 也是站点(1.25 的 F1 钉子)", () => {
80
+ withPlugin({ css: "::-webkit-scrollbar { width: 4px; }\n" }, (root) => {
81
+ const r = runSelectorFormCheck(root);
82
+ expect(r.anchorless).toHaveLength(1);
83
+ expect(r.anchorless[0].form.purePseudo).toBe(true);
84
+ });
85
+ });
86
+ it("负控⑦:拿不到 `pluginId`(无 plugin.json)⇒ fail-closed 红(与 `plugin-prefix.ts` 同款)", () => {
87
+ withPlugin({ manifest: null, css: "* { margin: 0; }\n" }, (root) => {
88
+ const r = runSelectorFormCheck(root);
89
+ expect(r.error).not.toBeNull();
90
+ expect(r.violations).toHaveLength(1);
91
+ expect(r.violations[0].file).toBe("plugin.json");
92
+ });
93
+ });
94
+ });
95
+ describe("判据 S3:跨方命中必须自带自有命名空间", () => {
96
+ it("负控⑤:`body .ldk-input { }` ⇒ S3 红(跨方命中 + 无自有锚)", () => {
97
+ withPlugin({ css: "body .ldk-input { height: 20px; }\n" }, (root) => {
98
+ const r = runSelectorFormCheck(root);
99
+ expect(r.anchorless).toHaveLength(0);
100
+ expect(r.crossParty).toHaveLength(1);
101
+ expect(r.crossParty[0].code).toBe("S3");
102
+ });
103
+ });
104
+ it("负控⑥:`.ldk-side-panel :focus-visible { outline: none }` ⇒ S3 红(🔴 F3 的钉子)", () => {
105
+ // 用 hasAncestor()/subjectOf() 判形态的实现会把这一条**漏掉**(它们剥掉伪类后只剩 `.ldk-side-panel`
106
+ // ⇒ 看起来「有主体、无祖先」⇒ 被当成一次顶层定义)——`formOf()` 认它有两个 compound ⇒ 形态是
107
+ // anchored ⇒ 交给 S3(跨方命中不带自有锚)来判。**这条就是本轴自带形态口径的理由。**
108
+ withPlugin({ css: ".ldk-side-panel :focus-visible { outline: none; }\n" }, (root) => {
109
+ const r = runSelectorFormCheck(root);
110
+ expect(r.anchorless).toHaveLength(0);
111
+ expect(r.crossParty).toHaveLength(1);
112
+ expect(r.crossParty[0].code).toBe("S3");
113
+ });
114
+ });
115
+ it("负控⑥b:`:has(.ldk-x)` 内的**提及**算提及;而**主体无锚**时归 S2(`(...)` 内不算锚)", () => {
116
+ // ① `body:has(.ldk-badge)`:`.ldk-badge` 在**过滤器**里 ⇒ 主体是 `body` ⇒ **顶层无锚** ⇒ S2
117
+ // (这正是 R0 的口径:「`(...)` 内的内容不算锚——那是过滤器不是主体」。)
118
+ withPlugin({ css: "body:has(.ldk-badge) { padding: 0; }\n" }, (root) => {
119
+ const r = runSelectorFormCheck(root);
120
+ expect(r.anchorless).toHaveLength(1);
121
+ expect(r.anchorless[0].code).toBe("S2");
122
+ expect(r.crossParty).toHaveLength(0);
123
+ });
124
+ // ② `main .other-root:has(.ldk-badge)`:有名字锚(`.other-root`)但**不是本仓前缀** ⇒ S3
125
+ withPlugin({ css: "main .other-root:has(.ldk-badge) { padding: 0; }\n" }, (root) => {
126
+ const r = runSelectorFormCheck(root);
127
+ expect(r.anchorless).toHaveLength(0);
128
+ expect(r.crossParty).toHaveLength(1);
129
+ expect(r.crossParty[0].selector).toBe("main .other-root:has(.ldk-badge)");
130
+ });
131
+ });
132
+ });
133
+ describe("正控:合规形态必须绿(正控条数 ≥ 负控条数)", () => {
134
+ it("正控①:`.serial-monitor-root input { }` ⇒ 绿(**限定有锚**——R2 的正确解法)", () => {
135
+ withPlugin({ manifest: { pluginId: "serial-monitor", name: "夹具", version: "1.0.0" }, css: ".serial-monitor-root input { height: 20px; }\n" }, (root) => {
136
+ expect(runSelectorFormCheck(root).violations).toHaveLength(0);
137
+ });
138
+ });
139
+ it("正控②:`.my-root :focus-visible { }` ⇒ 绿(🔴 **F3 反面钉子**:形态谓词必须认它有祖先)", () => {
140
+ withPlugin({ css: ".my-root :focus-visible { outline: 1px solid red; }\n" }, (root) => {
141
+ const r = runSelectorFormCheck(root);
142
+ expect(formOf(".my-root :focus-visible").kind).toBe("anchored");
143
+ expect(r.violations).toHaveLength(0);
144
+ });
145
+ });
146
+ it("正控③:`.settings-slider-control .ldk-slider { }` ⇒ 绿(R3 合规形态:跨方命中 + 自有前缀)", () => {
147
+ withPlugin({ manifest: { pluginId: "settings", name: "夹具", version: "1.0.0" }, css: ".settings-slider-control .ldk-slider { width: 100%; }\n" }, (root) => {
148
+ expect(runSelectorFormCheck(root).violations).toHaveLength(0);
149
+ });
150
+ });
151
+ it("正控④:`.marketplace-mpd-title-row .ldk-badge .codicon { }` ⇒ 绿(主体是三方类,仍带自有锚)", () => {
152
+ withPlugin({ manifest: { pluginId: "marketplace", name: "夹具", version: "1.0.0" }, css: ".marketplace-mpd-title-row .ldk-badge .codicon { font-size: 12px; }\n" }, (root) => {
153
+ expect(runSelectorFormCheck(root).violations).toHaveLength(0);
154
+ });
155
+ });
156
+ it("正控⑤:`.settings-row:has(.ldk-theme-picker) { }` ⇒ 绿(`:has()` 内的提及算提及、自有锚在主体上)", () => {
157
+ withPlugin({ manifest: { pluginId: "settings", name: "夹具", version: "1.0.0" }, css: ".settings-row:has(.ldk-theme-picker) { flex-direction: column; }\n" }, (root) => {
158
+ expect(runSelectorFormCheck(root).violations).toHaveLength(0);
159
+ });
160
+ });
161
+ it("正控⑥:`@keyframes` 体内的 `from` / `to` **不是选择器** ⇒ 绿", () => {
162
+ withPlugin({ css: ".demo-plugin-root { animation: demo-plugin-in 1s; }\n@keyframes demo-plugin-in {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n" }, (root) => {
163
+ expect(runSelectorFormCheck(root).violations).toHaveLength(0);
164
+ });
165
+ });
166
+ it("正控⑦:多选择器列表里**只报坏的那一份**(`button, .demo-plugin-root { }` ⇒ 1 处)", () => {
167
+ withPlugin({ css: "button,\n.demo-plugin-root { margin: 0; }\n" }, (root) => {
168
+ const r = runSelectorFormCheck(root);
169
+ expect(r.violations).toHaveLength(1);
170
+ expect(r.anchorless[0].selector).toBe("button");
171
+ });
172
+ });
173
+ it("正控⑧:同一份 CSS + 不同 `pluginId` ⇒ 不同裁决(证明这条腿在读 manifest,不是恒红/恒绿)", () => {
174
+ const css = ".other-ns-root .ldk-badge { color: red; }\n";
175
+ withPlugin({ manifest: { pluginId: "demo-plugin", name: "夹具", version: "1.0.0" }, css }, (root) => {
176
+ expect(runSelectorFormCheck(root).crossParty).toHaveLength(1); // `.other-ns-root` 不是我的前缀 ⇒ 红
177
+ });
178
+ withPlugin({ manifest: { pluginId: "other", name: "夹具", version: "1.0.0" }, css }, (root) => {
179
+ expect(runSelectorFormCheck(root).violations).toHaveLength(0); // `.other-` 就是本仓前缀 ⇒ 绿
180
+ });
181
+ });
182
+ it("正控⑨:`animation` 声明不是选择器(`animation: button 1s` 不该被当成元素选择器)", () => {
183
+ withPlugin({ css: ".demo-plugin-root { animation: demo-plugin-in 1s; }\n@keyframes demo-plugin-in { to { opacity: 1; } }\n" }, (root) => {
184
+ expect(runSelectorFormCheck(root).violations).toHaveLength(0);
185
+ });
186
+ });
187
+ });
188
+ describe("判定体 `judgeSelectorForm()` 与形态谓词 `formOf()`", () => {
189
+ it("判定体逐条符合预期(六形态 × 两判据)", () => {
190
+ const id = "demo-plugin";
191
+ // 绿:有锚、无 ldk 提及
192
+ expect(judgeSelectorForm({ selector: ".demo-plugin-root .a", pluginId: id })).toBeNull();
193
+ // S2:顶层无锚
194
+ expect(judgeSelectorForm({ selector: "select", pluginId: id })?.code).toBe("S2");
195
+ expect(judgeSelectorForm({ selector: ":focus-visible", pluginId: id })?.code).toBe("S2");
196
+ expect(judgeSelectorForm({ selector: "::-webkit-scrollbar", pluginId: id })?.code).toBe("S2");
197
+ // S2:限定无锚(B 段)
198
+ expect(judgeSelectorForm({ selector: "html body", pluginId: id })?.code).toBe("S2");
199
+ // S3:有锚但跨方命中不带自有锚
200
+ expect(judgeSelectorForm({ selector: ".a .ldk-badge", pluginId: id })?.code).toBe("S3");
201
+ });
202
+ it("`formOf()`:纯伪类/纯伪元素主体 ⇒ 无锚(不是无主体);`.a :focus-visible` 不是顶层无锚(F3 反面)", () => {
203
+ expect(formOf("::-webkit-scrollbar")).toEqual({ kind: "anchorless", top: true, purePseudo: true });
204
+ expect(formOf(":root")).toEqual({ kind: "anchorless", top: true, purePseudo: false });
205
+ expect(formOf("html body")).toEqual({ kind: "anchorless", top: false, purePseudo: false });
206
+ expect(formOf(".a :focus-visible").kind).toBe("anchored");
207
+ expect(formOf("#a").kind).toBe("anchored");
208
+ expect(formOf("div:has(.a)").kind).toBe("anchorless"); // `(...)` 内不算锚(过滤器不是主体)
209
+ });
210
+ });
211
+ //# sourceMappingURL=selector-form.test.js.map