@open-feishu-card/language-server 0.1.2 → 0.1.3

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.cjs ADDED
@@ -0,0 +1,167 @@
1
+ 'use strict';
2
+
3
+ var parser = require('@open-feishu-card/parser');
4
+ var compiler = require('@open-feishu-card/compiler');
5
+ var validator = require('@open-feishu-card/validator');
6
+ var linter = require('@open-feishu-card/linter');
7
+
8
+ // src/index.ts
9
+ function toLspRange(r) {
10
+ return {
11
+ start: { line: r.start.line - 1, character: r.start.column - 1 },
12
+ end: { line: r.end.line - 1, character: r.end.column - 1 }
13
+ };
14
+ }
15
+ function analyze(source) {
16
+ const result = parser.parse(source);
17
+ const vdiags = validator.validateAst(result);
18
+ const ldiags = linter.lintAst(result.ast);
19
+ const diagnostics = [
20
+ ...vdiags.map((d) => ({
21
+ range: toLspRange(d.range),
22
+ message: d.message,
23
+ severity: d.severity === "error" ? 1 : d.severity === "warning" ? 2 : d.severity === "information" ? 3 : 4,
24
+ source: "open-feishu-card",
25
+ code: d.code
26
+ })),
27
+ ...ldiags.map((d) => ({
28
+ range: toLspRange(d.range),
29
+ message: `[${d.rule}] ${d.message}`,
30
+ severity: d.severity === "warning" ? 2 : 3,
31
+ source: "open-feishu-card",
32
+ code: d.rule
33
+ }))
34
+ ];
35
+ return { diagnostics, ast: result.ast };
36
+ }
37
+ var CARD_TOP_LEVEL_KEYS = [
38
+ { label: "schema", kind: 14, detail: '\u5FC5\u586B\uFF0C\u56FA\u5B9A "2.0"' },
39
+ { label: "config", kind: 14, detail: "\u5361\u7247\u914D\u7F6E\uFF08update_multi / enable_forward / style \u7B49\uFF09" },
40
+ { label: "header", kind: 14, detail: "\u5361\u7247\u5934\uFF08title / subtitle / template / ud_icon\uFF09" },
41
+ { label: "body", kind: 14, detail: "\u5361\u7247\u6B63\u6587 { elements: [...] }" },
42
+ { label: "footnote", kind: 14, detail: "\u5361\u7247\u811A\u6CE8" }
43
+ ];
44
+ var ELEMENT_TAGS = [
45
+ "markdown",
46
+ "div",
47
+ "column_set",
48
+ "column",
49
+ "hr",
50
+ "image",
51
+ "button",
52
+ "overflow",
53
+ "select_static",
54
+ "select_multi",
55
+ "input",
56
+ "date_picker",
57
+ "picker_datetime",
58
+ "checker",
59
+ "chart",
60
+ "table",
61
+ "iframe",
62
+ "person",
63
+ "person_list",
64
+ "interactive_container",
65
+ "tag",
66
+ "carousel",
67
+ "grid"
68
+ ];
69
+ var ELEMENT_TAG_COMPLETIONS = ELEMENT_TAGS.map((t) => ({
70
+ label: t,
71
+ kind: 22,
72
+ // Struct
73
+ detail: `\u98DE\u4E66\u5361\u7247\u7EC4\u4EF6 ${t}`,
74
+ insertText: t
75
+ }));
76
+ var COMMON_PROPS = [
77
+ { label: "text_align", kind: 10, detail: "\u6587\u672C\u5BF9\u9F50\uFF1Aleft | center | right" },
78
+ { label: "padding", kind: 10, detail: '\u5185\u8FB9\u8DDD\uFF0C\u5982 "12px" \u6216 "12px 8px"' },
79
+ { label: "margin", kind: 10, detail: "\u5916\u8FB9\u8DDD" },
80
+ { label: "background_style", kind: 10, detail: "\u80CC\u666F\u6837\u5F0F" },
81
+ { label: "behaviors", kind: 10, detail: '\u70B9\u51FB\u884C\u4E3A\u6570\u7EC4 [{type:"open_url", default_url}]' },
82
+ { label: "icon", kind: 10, detail: "\u524D\u7F6E\u56FE\u6807\u5143\u7D20" },
83
+ { label: "text_size", kind: 10, detail: "\u5B57\u53F7\uFF1Anormal | small | large" },
84
+ { label: "text_color", kind: 10, detail: "\u6587\u672C\u989C\u8272\uFF0C\u5982 default / green / red" }
85
+ ];
86
+ function complete(source, offset) {
87
+ const before = source.slice(0, offset);
88
+ const tagCtx = /"tag"\s*:\s*"([^"]*)$/.exec(before);
89
+ if (tagCtx) return ELEMENT_TAG_COMPLETIONS;
90
+ return [...CARD_TOP_LEVEL_KEYS, ...COMMON_PROPS];
91
+ }
92
+ var HOVER_DOCS = {
93
+ schema: '\u5361\u7247 JSON \u7ED3\u6784\u7248\u672C\uFF0C2.0 \u56FA\u5B9A\u4E3A "2.0"\u3002',
94
+ config: "\u5361\u7247\u7EA7\u914D\u7F6E\uFF1Aupdate_multi\uFF08\u591A\u6B21\u66F4\u65B0\uFF09\u3001enable_forward\u3001style\u3001locale \u7B49\u3002",
95
+ header: "\u5361\u7247\u5934\u90E8\uFF1Atitle\u3001subtitle\u3001template\uFF08\u989C\u8272\u6A21\u677F\uFF09\u3001ud_icon\u3001text_tag_list\u3002",
96
+ body: "\u5361\u7247\u6B63\u6587\u5BB9\u5668\uFF0Celements \u4E3A\u7EC4\u4EF6\u6570\u7EC4\u3002",
97
+ elements: "\u7EC4\u4EF6\u6570\u7EC4\uFF0C\u6BCF\u9879\u662F { tag, ...\u5C5E\u6027 }\u3002",
98
+ tag: "\u7EC4\u4EF6\u7C7B\u578B\u6807\u8BC6\uFF0C\u5982 markdown / column_set / button / chart\u3002",
99
+ markdown: "Markdown \u7EC4\u4EF6\uFF1Acontent \u652F\u6301 md \u8BED\u6CD5\u4E0E\u98DE\u4E66\u6269\u5C55\u6807\u7B7E\uFF08<font>\u3001<person> \u7B49\uFF09\u3002",
100
+ column_set: "\u591A\u5217\u5E03\u5C40\u5BB9\u5668\uFF0Cflex_mode \u63A7\u5236 \u5747\u5206/\u5B9A\u5BBD/\u81EA\u9002\u5E94\u3002",
101
+ update_multi: "config \u5B57\u6BB5\uFF1A\u540C\u4E00\u5361\u7247\u662F\u5426\u5141\u8BB8\u591A\u6B21\u66F4\u65B0\u53E0\u52A0\u3002"
102
+ };
103
+ function hover(source, offset) {
104
+ const before = source.slice(0, offset);
105
+ const after = source.slice(offset);
106
+ const m = /"?([a-z_]*)$/.exec(before);
107
+ const rest = /^([a-z_]*)/.exec(after)?.[1] ?? "";
108
+ const key = (m?.[1] ?? "") + rest;
109
+ const doc = key && HOVER_DOCS[key];
110
+ if (!doc) return void 0;
111
+ return { contents: { language: "markdown", value: `**${key}** \u2014 ${doc}` } };
112
+ }
113
+ function quickFixes(source, range) {
114
+ const actions = [];
115
+ const { diagnostics, ast } = analyze(source);
116
+ const inRange = diagnostics.filter(
117
+ (d) => (d.code === "deprecated-prop" || d.code === "unrecognized-key") && d.range.start.line >= range.start.line && d.range.end.line <= range.end.line
118
+ );
119
+ for (const d of inRange) {
120
+ actions.push({
121
+ title: `\u5220\u9664\u5C5E\u6027\uFF08${d.message.split(":")[0]}\uFF09`,
122
+ kind: "quickfix",
123
+ isPreferred: true,
124
+ edits: [
125
+ {
126
+ // 覆盖 key(含引号与冒号前的空白),删除后留空
127
+ range: {
128
+ start: { line: d.range.start.line, character: d.range.start.character - 1 },
129
+ end: { line: d.range.start.line, character: d.range.start.character + `${d.message.split('"')[1] ?? ""}`.length + 1 }
130
+ },
131
+ newText: ""
132
+ }
133
+ ]
134
+ });
135
+ }
136
+ return actions;
137
+ }
138
+ function sanitizeAllAction(source) {
139
+ const { ast, data } = { ast: parser.parse(source).ast, data: parser.parse(source).data };
140
+ if (!ast || data === void 0) return void 0;
141
+ const { stripped, card } = compiler.sanitize(data, { mode: "strip" });
142
+ if (stripped.length === 0) return void 0;
143
+ const lines = source.split("\n");
144
+ const lastLine = lines.length - 1;
145
+ return {
146
+ title: `open-feishu-card: \u51C0\u5316\u5361\u7247\uFF08\u5265\u79BB ${stripped.length} \u4E2A\u975E\u6807\u5C5E\u6027\uFF09`,
147
+ kind: "source.fixAll",
148
+ isPreferred: true,
149
+ edits: [
150
+ {
151
+ range: {
152
+ start: { line: 0, character: 0 },
153
+ end: { line: lastLine, character: lines[lastLine].length }
154
+ },
155
+ newText: JSON.stringify(card, null, 2) + "\n"
156
+ }
157
+ ]
158
+ };
159
+ }
160
+
161
+ exports.CARD_TOP_LEVEL_KEYS = CARD_TOP_LEVEL_KEYS;
162
+ exports.ELEMENT_TAG_COMPLETIONS = ELEMENT_TAG_COMPLETIONS;
163
+ exports.analyze = analyze;
164
+ exports.complete = complete;
165
+ exports.hover = hover;
166
+ exports.quickFixes = quickFixes;
167
+ exports.sanitizeAllAction = sanitizeAllAction;
package/dist/index.js CHANGED
@@ -1,171 +1,159 @@
1
- /**
2
- * @open-feishu-card/language-server
3
- *
4
- * 轻量 LSP 语言服务器核心(与传输层解耦):
5
- * - 分析:validate (Schema) + lint (最佳实践) → LSP Diagnostics
6
- * - 补全:卡片顶层字段、组件 tag、常用属性名
7
- * - Hover:基于 Schema 描述的属性提示
8
- * - Code Action:非标属性一键删除(Quick Fix,调 compiler.sanitize 定位路径)
9
- *
10
- * 传输层(stdio JSON-RPC)在 server.ts,可运行于 Node.js / WebWorker。
11
- */
12
1
  import { parse } from '@open-feishu-card/parser';
13
2
  import { sanitize } from '@open-feishu-card/compiler';
14
3
  import { validateAst } from '@open-feishu-card/validator';
15
4
  import { lintAst } from '@open-feishu-card/linter';
5
+
6
+ // src/index.ts
16
7
  function toLspRange(r) {
17
- return {
18
- start: { line: r.start.line - 1, character: r.start.column - 1 },
19
- end: { line: r.end.line - 1, character: r.end.column - 1 },
20
- };
8
+ return {
9
+ start: { line: r.start.line - 1, character: r.start.column - 1 },
10
+ end: { line: r.end.line - 1, character: r.end.column - 1 }
11
+ };
21
12
  }
22
- /** 分析一份文档源码,输出 LSP 诊断 */
23
- export function analyze(source) {
24
- const result = parse(source);
25
- const vdiags = validateAst(result);
26
- const ldiags = lintAst(result.ast);
27
- const diagnostics = [
28
- ...vdiags.map((d) => ({
29
- range: toLspRange(d.range),
30
- message: d.message,
31
- severity: (d.severity === 'error' ? 1 : d.severity === 'warning' ? 2 : d.severity === 'information' ? 3 : 4),
32
- source: 'open-feishu-card',
33
- code: d.code,
34
- })),
35
- ...ldiags.map((d) => ({
36
- range: toLspRange(d.range),
37
- message: `[${d.rule}] ${d.message}`,
38
- severity: (d.severity === 'warning' ? 2 : 3),
39
- source: 'open-feishu-card',
40
- code: d.rule,
41
- })),
42
- ];
43
- return { diagnostics, ast: result.ast };
13
+ function analyze(source) {
14
+ const result = parse(source);
15
+ const vdiags = validateAst(result);
16
+ const ldiags = lintAst(result.ast);
17
+ const diagnostics = [
18
+ ...vdiags.map((d) => ({
19
+ range: toLspRange(d.range),
20
+ message: d.message,
21
+ severity: d.severity === "error" ? 1 : d.severity === "warning" ? 2 : d.severity === "information" ? 3 : 4,
22
+ source: "open-feishu-card",
23
+ code: d.code
24
+ })),
25
+ ...ldiags.map((d) => ({
26
+ range: toLspRange(d.range),
27
+ message: `[${d.rule}] ${d.message}`,
28
+ severity: d.severity === "warning" ? 2 : 3,
29
+ source: "open-feishu-card",
30
+ code: d.rule
31
+ }))
32
+ ];
33
+ return { diagnostics, ast: result.ast };
44
34
  }
45
- /* ------------------------------------------------------------------ *
46
- * 补全
47
- * ------------------------------------------------------------------ */
48
- export const CARD_TOP_LEVEL_KEYS = [
49
- { label: 'schema', kind: 14, detail: '必填,固定 "2.0"' },
50
- { label: 'config', kind: 14, detail: '卡片配置(update_multi / enable_forward / style 等)' },
51
- { label: 'header', kind: 14, detail: '卡片头(title / subtitle / template / ud_icon)' },
52
- { label: 'body', kind: 14, detail: '卡片正文 { elements: [...] }' },
53
- { label: 'footnote', kind: 14, detail: '卡片脚注' },
35
+ var CARD_TOP_LEVEL_KEYS = [
36
+ { label: "schema", kind: 14, detail: '\u5FC5\u586B\uFF0C\u56FA\u5B9A "2.0"' },
37
+ { label: "config", kind: 14, detail: "\u5361\u7247\u914D\u7F6E\uFF08update_multi / enable_forward / style \u7B49\uFF09" },
38
+ { label: "header", kind: 14, detail: "\u5361\u7247\u5934\uFF08title / subtitle / template / ud_icon\uFF09" },
39
+ { label: "body", kind: 14, detail: "\u5361\u7247\u6B63\u6587 { elements: [...] }" },
40
+ { label: "footnote", kind: 14, detail: "\u5361\u7247\u811A\u6CE8" }
54
41
  ];
55
- const ELEMENT_TAGS = [
56
- 'markdown', 'div', 'column_set', 'column', 'hr', 'image', 'button', 'overflow',
57
- 'select_static', 'select_multi', 'input', 'date_picker', 'picker_datetime',
58
- 'checker', 'chart', 'table', 'iframe', 'person', 'person_list',
59
- 'interactive_container', 'tag', 'carousel', 'grid',
42
+ var ELEMENT_TAGS = [
43
+ "markdown",
44
+ "div",
45
+ "column_set",
46
+ "column",
47
+ "hr",
48
+ "image",
49
+ "button",
50
+ "overflow",
51
+ "select_static",
52
+ "select_multi",
53
+ "input",
54
+ "date_picker",
55
+ "picker_datetime",
56
+ "checker",
57
+ "chart",
58
+ "table",
59
+ "iframe",
60
+ "person",
61
+ "person_list",
62
+ "interactive_container",
63
+ "tag",
64
+ "carousel",
65
+ "grid"
60
66
  ];
61
- export const ELEMENT_TAG_COMPLETIONS = ELEMENT_TAGS.map((t) => ({
62
- label: t,
63
- kind: 22, // Struct
64
- detail: `飞书卡片组件 ${t}`,
65
- insertText: t,
67
+ var ELEMENT_TAG_COMPLETIONS = ELEMENT_TAGS.map((t) => ({
68
+ label: t,
69
+ kind: 22,
70
+ // Struct
71
+ detail: `\u98DE\u4E66\u5361\u7247\u7EC4\u4EF6 ${t}`,
72
+ insertText: t
66
73
  }));
67
- const COMMON_PROPS = [
68
- { label: 'text_align', kind: 10, detail: '文本对齐:left | center | right' },
69
- { label: 'padding', kind: 10, detail: '内边距,如 "12px" 或 "12px 8px"' },
70
- { label: 'margin', kind: 10, detail: '外边距' },
71
- { label: 'background_style', kind: 10, detail: '背景样式' },
72
- { label: 'behaviors', kind: 10, detail: '点击行为数组 [{type:"open_url", default_url}]' },
73
- { label: 'icon', kind: 10, detail: '前置图标元素' },
74
- { label: 'text_size', kind: 10, detail: '字号:normal | small | large' },
75
- { label: 'text_color', kind: 10, detail: '文本颜色,如 default / green / red' },
74
+ var COMMON_PROPS = [
75
+ { label: "text_align", kind: 10, detail: "\u6587\u672C\u5BF9\u9F50\uFF1Aleft | center | right" },
76
+ { label: "padding", kind: 10, detail: '\u5185\u8FB9\u8DDD\uFF0C\u5982 "12px" \u6216 "12px 8px"' },
77
+ { label: "margin", kind: 10, detail: "\u5916\u8FB9\u8DDD" },
78
+ { label: "background_style", kind: 10, detail: "\u80CC\u666F\u6837\u5F0F" },
79
+ { label: "behaviors", kind: 10, detail: '\u70B9\u51FB\u884C\u4E3A\u6570\u7EC4 [{type:"open_url", default_url}]' },
80
+ { label: "icon", kind: 10, detail: "\u524D\u7F6E\u56FE\u6807\u5143\u7D20" },
81
+ { label: "text_size", kind: 10, detail: "\u5B57\u53F7\uFF1Anormal | small | large" },
82
+ { label: "text_color", kind: 10, detail: "\u6587\u672C\u989C\u8272\uFF0C\u5982 default / green / red" }
76
83
  ];
77
- /**
78
- * 上下文感知补全:
79
- * - 光标在 "tag": "..." 值里 → 组件 tag 列表
80
- * - 光标在对象 key 位置 → 顶层字段或通用属性
81
- */
82
- export function complete(source, offset) {
83
- // 找光标前最近的一个未闭合的 "tag": 上下文
84
- const before = source.slice(0, offset);
85
- const tagCtx = /"tag"\s*:\s*"([^"]*)$/.exec(before);
86
- if (tagCtx)
87
- return ELEMENT_TAG_COMPLETIONS;
88
- // 顶层对象的属性补全:光标前没有更深的嵌套证据时给全量
89
- return [...CARD_TOP_LEVEL_KEYS, ...COMMON_PROPS];
84
+ function complete(source, offset) {
85
+ const before = source.slice(0, offset);
86
+ const tagCtx = /"tag"\s*:\s*"([^"]*)$/.exec(before);
87
+ if (tagCtx) return ELEMENT_TAG_COMPLETIONS;
88
+ return [...CARD_TOP_LEVEL_KEYS, ...COMMON_PROPS];
90
89
  }
91
- /* ------------------------------------------------------------------ *
92
- * Hover
93
- * ------------------------------------------------------------------ */
94
- const HOVER_DOCS = {
95
- schema: '卡片 JSON 结构版本,2.0 固定为 "2.0"。',
96
- config: '卡片级配置:update_multi(多次更新)、enable_forward、style、locale 等。',
97
- header: '卡片头部:title、subtitle、template(颜色模板)、ud_icon、text_tag_list。',
98
- body: '卡片正文容器,elements 为组件数组。',
99
- elements: '组件数组,每项是 { tag, ...属性 }。',
100
- tag: '组件类型标识,如 markdown / column_set / button / chart。',
101
- markdown: 'Markdown 组件:content 支持 md 语法与飞书扩展标签(<font>、<person> 等)。',
102
- column_set: '多列布局容器,flex_mode 控制 均分/定宽/自适应。',
103
- update_multi: 'config 字段:同一卡片是否允许多次更新叠加。',
90
+ var HOVER_DOCS = {
91
+ schema: '\u5361\u7247 JSON \u7ED3\u6784\u7248\u672C\uFF0C2.0 \u56FA\u5B9A\u4E3A "2.0"\u3002',
92
+ config: "\u5361\u7247\u7EA7\u914D\u7F6E\uFF1Aupdate_multi\uFF08\u591A\u6B21\u66F4\u65B0\uFF09\u3001enable_forward\u3001style\u3001locale \u7B49\u3002",
93
+ header: "\u5361\u7247\u5934\u90E8\uFF1Atitle\u3001subtitle\u3001template\uFF08\u989C\u8272\u6A21\u677F\uFF09\u3001ud_icon\u3001text_tag_list\u3002",
94
+ body: "\u5361\u7247\u6B63\u6587\u5BB9\u5668\uFF0Celements \u4E3A\u7EC4\u4EF6\u6570\u7EC4\u3002",
95
+ elements: "\u7EC4\u4EF6\u6570\u7EC4\uFF0C\u6BCF\u9879\u662F { tag, ...\u5C5E\u6027 }\u3002",
96
+ tag: "\u7EC4\u4EF6\u7C7B\u578B\u6807\u8BC6\uFF0C\u5982 markdown / column_set / button / chart\u3002",
97
+ markdown: "Markdown \u7EC4\u4EF6\uFF1Acontent \u652F\u6301 md \u8BED\u6CD5\u4E0E\u98DE\u4E66\u6269\u5C55\u6807\u7B7E\uFF08<font>\u3001<person> \u7B49\uFF09\u3002",
98
+ column_set: "\u591A\u5217\u5E03\u5C40\u5BB9\u5668\uFF0Cflex_mode \u63A7\u5236 \u5747\u5206/\u5B9A\u5BBD/\u81EA\u9002\u5E94\u3002",
99
+ update_multi: "config \u5B57\u6BB5\uFF1A\u540C\u4E00\u5361\u7247\u662F\u5426\u5141\u8BB8\u591A\u6B21\u66F4\u65B0\u53E0\u52A0\u3002"
104
100
  };
105
- export function hover(source, offset) {
106
- const before = source.slice(0, offset);
107
- const after = source.slice(offset);
108
- // 光标可能位于 key 的引号内任意位置:取光标前的部分 key + 光标后的剩余 key
109
- const m = /"?([a-z_]*)$/.exec(before);
110
- const rest = /^([a-z_]*)/.exec(after)?.[1] ?? '';
111
- const key = (m?.[1] ?? '') + rest;
112
- const doc = key && HOVER_DOCS[key];
113
- if (!doc)
114
- return undefined;
115
- return { contents: { language: 'markdown', value: `**${key}** — ${doc}` } };
101
+ function hover(source, offset) {
102
+ const before = source.slice(0, offset);
103
+ const after = source.slice(offset);
104
+ const m = /"?([a-z_]*)$/.exec(before);
105
+ const rest = /^([a-z_]*)/.exec(after)?.[1] ?? "";
106
+ const key = (m?.[1] ?? "") + rest;
107
+ const doc = key && HOVER_DOCS[key];
108
+ if (!doc) return void 0;
109
+ return { contents: { language: "markdown", value: `**${key}** \u2014 ${doc}` } };
116
110
  }
117
- /* ------------------------------------------------------------------ *
118
- * Code Action(Quick Fix)
119
- * ------------------------------------------------------------------ */
120
- /** 对指定范围给出快速修复:删除非标/废弃属性 */
121
- export function quickFixes(source, range) {
122
- const actions = [];
123
- const { diagnostics, ast } = analyze(source);
124
- const inRange = diagnostics.filter((d) => (d.code === 'deprecated-prop' || d.code === 'unrecognized-key') &&
125
- d.range.start.line >= range.start.line &&
126
- d.range.end.line <= range.end.line);
127
- for (const d of inRange) {
128
- actions.push({
129
- title: `删除属性(${d.message.split(':')[0]})`,
130
- kind: 'quickfix',
131
- isPreferred: true,
132
- edits: [
133
- {
134
- // 覆盖 key(含引号与冒号前的空白),删除后留空
135
- range: {
136
- start: { line: d.range.start.line, character: d.range.start.character - 1 },
137
- end: { line: d.range.start.line, character: d.range.start.character + `${d.message.split('"')[1] ?? ''}`.length + 1 },
138
- },
139
- newText: '',
140
- },
141
- ],
142
- });
143
- }
144
- return actions;
111
+ function quickFixes(source, range) {
112
+ const actions = [];
113
+ const { diagnostics, ast } = analyze(source);
114
+ const inRange = diagnostics.filter(
115
+ (d) => (d.code === "deprecated-prop" || d.code === "unrecognized-key") && d.range.start.line >= range.start.line && d.range.end.line <= range.end.line
116
+ );
117
+ for (const d of inRange) {
118
+ actions.push({
119
+ title: `\u5220\u9664\u5C5E\u6027\uFF08${d.message.split(":")[0]}\uFF09`,
120
+ kind: "quickfix",
121
+ isPreferred: true,
122
+ edits: [
123
+ {
124
+ // 覆盖 key(含引号与冒号前的空白),删除后留空
125
+ range: {
126
+ start: { line: d.range.start.line, character: d.range.start.character - 1 },
127
+ end: { line: d.range.start.line, character: d.range.start.character + `${d.message.split('"')[1] ?? ""}`.length + 1 }
128
+ },
129
+ newText: ""
130
+ }
131
+ ]
132
+ });
133
+ }
134
+ return actions;
145
135
  }
146
- /** 全文档级快速修复:一键净化(剥离全部非标属性),返回整文档替换编辑 */
147
- export function sanitizeAllAction(source) {
148
- const { ast, data } = { ast: parse(source).ast, data: parse(source).data };
149
- if (!ast || data === undefined)
150
- return undefined;
151
- const { stripped, card } = sanitize(data, { mode: 'strip' });
152
- if (stripped.length === 0)
153
- return undefined;
154
- const lines = source.split('\n');
155
- const lastLine = lines.length - 1;
156
- return {
157
- title: `open-feishu-card: 净化卡片(剥离 ${stripped.length} 个非标属性)`,
158
- kind: 'source.fixAll',
159
- isPreferred: true,
160
- edits: [
161
- {
162
- range: {
163
- start: { line: 0, character: 0 },
164
- end: { line: lastLine, character: lines[lastLine].length },
165
- },
166
- newText: JSON.stringify(card, null, 2) + '\n',
167
- },
168
- ],
169
- };
136
+ function sanitizeAllAction(source) {
137
+ const { ast, data } = { ast: parse(source).ast, data: parse(source).data };
138
+ if (!ast || data === void 0) return void 0;
139
+ const { stripped, card } = sanitize(data, { mode: "strip" });
140
+ if (stripped.length === 0) return void 0;
141
+ const lines = source.split("\n");
142
+ const lastLine = lines.length - 1;
143
+ return {
144
+ title: `open-feishu-card: \u51C0\u5316\u5361\u7247\uFF08\u5265\u79BB ${stripped.length} \u4E2A\u975E\u6807\u5C5E\u6027\uFF09`,
145
+ kind: "source.fixAll",
146
+ isPreferred: true,
147
+ edits: [
148
+ {
149
+ range: {
150
+ start: { line: 0, character: 0 },
151
+ end: { line: lastLine, character: lines[lastLine].length }
152
+ },
153
+ newText: JSON.stringify(card, null, 2) + "\n"
154
+ }
155
+ ]
156
+ };
170
157
  }
171
- //# sourceMappingURL=index.js.map
158
+
159
+ export { CARD_TOP_LEVEL_KEYS, ELEMENT_TAG_COMPLETIONS, analyze, complete, hover, quickFixes, sanitizeAllAction };