@zenithbuild/language-server 0.6.0 → 0.6.17

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.
@@ -1,219 +0,0 @@
1
- import {
2
- ZenithDiagnostic,
3
- ZenithRange
4
- } from './diagnostics';
5
-
6
- export const EVENT_BINDING_DIAGNOSTIC_CODE = 'zenith.event.binding.syntax';
7
-
8
- export const ZEN_DOM_QUERY = 'ZEN-DOM-QUERY';
9
- export const ZEN_DOM_LISTENER = 'ZEN-DOM-LISTENER';
10
- export const ZEN_DOM_WRAPPER = 'ZEN-DOM-WRAPPER';
11
-
12
- export interface EventBindingCodeActionData {
13
- replacement: string;
14
- title: string;
15
- }
16
-
17
- export interface ZenithCodeAction {
18
- title: string;
19
- kind: string;
20
- diagnostics: ZenithDiagnostic[];
21
- edit: {
22
- changes: Record<string, Array<{ range: ZenithRange; newText: string }>>;
23
- };
24
- isPreferred?: boolean;
25
- }
26
-
27
- interface ZenithTextDocumentLike {
28
- uri: string;
29
- getText(): string;
30
- positionAt(offset: number): { line: number; character: number };
31
- offsetAt(position: { line: number; character: number }): number;
32
- }
33
-
34
- export function buildEventBindingCodeActions(
35
- document: ZenithTextDocumentLike,
36
- diagnostics: ZenithDiagnostic[]
37
- ): ZenithCodeAction[] {
38
- const actions: ZenithCodeAction[] = [];
39
-
40
- for (const diagnostic of diagnostics) {
41
- if (diagnostic.code !== EVENT_BINDING_DIAGNOSTIC_CODE) {
42
- continue;
43
- }
44
-
45
- const data = diagnostic.data as EventBindingCodeActionData | undefined;
46
- if (!data || typeof data.replacement !== 'string' || typeof data.title !== 'string') {
47
- continue;
48
- }
49
-
50
- actions.push({
51
- title: data.title,
52
- kind: 'quickfix',
53
- diagnostics: [diagnostic],
54
- edit: {
55
- changes: {
56
- [document.uri]: [{
57
- range: diagnostic.range,
58
- newText: data.replacement
59
- }]
60
- }
61
- },
62
- isPreferred: true
63
- });
64
- }
65
-
66
- return actions;
67
- }
68
-
69
- export function buildDomLintCodeActions(
70
- document: ZenithTextDocumentLike,
71
- diagnostics: ZenithDiagnostic[]
72
- ): ZenithCodeAction[] {
73
- const actions: ZenithCodeAction[] = [];
74
- const text = document.getText();
75
-
76
- for (const diagnostic of diagnostics) {
77
- const code = diagnostic.code;
78
- if (code !== ZEN_DOM_QUERY && code !== ZEN_DOM_LISTENER && code !== ZEN_DOM_WRAPPER) {
79
- continue;
80
- }
81
-
82
- const startOffset = document.offsetAt(diagnostic.range.start);
83
- const endOffset = document.offsetAt(diagnostic.range.end);
84
- const lineStart = text.lastIndexOf('\n', startOffset) + 1;
85
- const lineEnd = text.indexOf('\n', endOffset);
86
- const lineEndOffset = lineEnd === -1 ? text.length : lineEnd;
87
- const lineContent = text.substring(lineStart, lineEndOffset);
88
-
89
- if (code === ZEN_DOM_QUERY) {
90
- const insertPos = { line: diagnostic.range.start.line, character: 0 };
91
- actions.push({
92
- title: 'Suppress with // zen-allow:dom-query <reason>',
93
- kind: 'quickfix',
94
- diagnostics: [diagnostic],
95
- edit: {
96
- changes: {
97
- [document.uri]: [{
98
- range: { start: insertPos, end: insertPos },
99
- newText: '// zen-allow:dom-query <reason>\n'
100
- }]
101
- }
102
- }
103
- });
104
- actions.push({
105
- title: 'Convert to ref() (partial / TODO)',
106
- kind: 'quickfix',
107
- diagnostics: [diagnostic],
108
- edit: {
109
- changes: {
110
- [document.uri]: [{
111
- range: { start: insertPos, end: insertPos },
112
- newText: '// TODO: use ref<T>() + zenMount instead\nconst elRef = ref<HTMLElement>();\n'
113
- }]
114
- }
115
- }
116
- });
117
- } else if (code === ZEN_DOM_LISTENER) {
118
- const insertPos = { line: diagnostic.range.start.line, character: 0 };
119
- const lineRange = {
120
- start: document.positionAt(lineStart),
121
- end: document.positionAt(lineEndOffset)
122
- };
123
- const commentedLine = lineContent.replace(/^(\s*)/, '$1// ');
124
- actions.push({
125
- title: 'Replace with zenOn template',
126
- kind: 'quickfix',
127
- diagnostics: [diagnostic],
128
- edit: {
129
- changes: {
130
- [document.uri]: [
131
- {
132
- range: { start: insertPos, end: insertPos },
133
- newText: '// zenOn(target, eventName, handler) - register disposer via ctx.cleanup\n// const off = zenOn(doc, \'keydown\', handler); ctx.cleanup(off);\n'
134
- },
135
- {
136
- range: lineRange,
137
- newText: commentedLine
138
- }
139
- ]
140
- }
141
- }
142
- });
143
- } else if (code === ZEN_DOM_WRAPPER) {
144
- let newText = lineContent;
145
- if (lineContent.includes('window') && !lineContent.includes('zenWindow')) {
146
- newText = newText.replace(/\bwindow\b/g, 'zenWindow()');
147
- }
148
- if (lineContent.includes('document') && !lineContent.includes('zenDocument')) {
149
- newText = newText.replace(/\bdocument\b/g, 'zenDocument()');
150
- }
151
- if (lineContent.includes('globalThis.window')) {
152
- newText = newText.replace(/globalThis\.window/g, 'zenWindow()');
153
- }
154
- if (lineContent.includes('globalThis.document')) {
155
- newText = newText.replace(/globalThis\.document/g, 'zenDocument()');
156
- }
157
- if (newText !== lineContent) {
158
- actions.push({
159
- title: 'Replace with zenWindow() / zenDocument()',
160
- kind: 'quickfix',
161
- diagnostics: [diagnostic],
162
- edit: {
163
- changes: {
164
- [document.uri]: [{
165
- range: {
166
- start: document.positionAt(lineStart),
167
- end: document.positionAt(lineEndOffset)
168
- },
169
- newText
170
- }]
171
- }
172
- }
173
- });
174
- }
175
- }
176
- }
177
-
178
- return actions;
179
- }
180
-
181
- /**
182
- * Convenience code actions: Replace window/document with zenWindow()/zenDocument()
183
- * even when there is no ZEN-DOM-WRAPPER diagnostic.
184
- */
185
- export function buildWindowDocumentCodeActions(
186
- document: ZenithTextDocumentLike,
187
- range: ZenithRange
188
- ): ZenithCodeAction[] {
189
- const text = document.getText();
190
- const startOffset = document.offsetAt(range.start);
191
- const endOffset = document.offsetAt(range.end);
192
- const selected = text.substring(startOffset, endOffset);
193
-
194
- if (selected === 'window') {
195
- return [{
196
- title: 'Replace with zenWindow()',
197
- kind: 'refactor',
198
- diagnostics: [],
199
- edit: {
200
- changes: {
201
- [document.uri]: [{ range, newText: 'zenWindow()' }]
202
- }
203
- }
204
- }];
205
- }
206
- if (selected === 'document') {
207
- return [{
208
- title: 'Replace with zenDocument()',
209
- kind: 'refactor',
210
- diagnostics: [],
211
- edit: {
212
- changes: {
213
- [document.uri]: [{ range, newText: 'zenDocument()' }]
214
- }
215
- }
216
- }];
217
- }
218
- return [];
219
- }
package/src/contracts.ts DELETED
@@ -1,100 +0,0 @@
1
- import * as fs from 'fs';
2
- import * as path from 'path';
3
-
4
- export type ZenithFileKind = 'page' | 'layout' | 'component' | 'unknown';
5
-
6
- export function stripImportSuffix(specifier: string): string {
7
- const hashIndex = specifier.indexOf('#');
8
- const queryIndex = specifier.indexOf('?');
9
-
10
- let cutAt = -1;
11
- if (hashIndex >= 0 && queryIndex >= 0) {
12
- cutAt = Math.min(hashIndex, queryIndex);
13
- } else if (hashIndex >= 0) {
14
- cutAt = hashIndex;
15
- } else if (queryIndex >= 0) {
16
- cutAt = queryIndex;
17
- }
18
-
19
- return cutAt >= 0 ? specifier.slice(0, cutAt) : specifier;
20
- }
21
-
22
- export function isLocalCssSpecifier(specifier: string): boolean {
23
- return (
24
- specifier.startsWith('./') ||
25
- specifier.startsWith('../') ||
26
- specifier.startsWith('/')
27
- );
28
- }
29
-
30
- export function isCssContractImportSpecifier(specifier: string): boolean {
31
- const normalized = stripImportSuffix(specifier).trim();
32
- if (!normalized) {
33
- return false;
34
- }
35
-
36
- if (normalized.endsWith('.css')) {
37
- return true;
38
- }
39
-
40
- if (normalized === 'tailwindcss') {
41
- return true;
42
- }
43
-
44
- if (/^@[^/]+\/css(?:$|\/)/.test(normalized)) {
45
- return true;
46
- }
47
-
48
- return false;
49
- }
50
-
51
- function canonicalizePath(candidate: string): string {
52
- try {
53
- return fs.realpathSync.native(candidate);
54
- } catch {
55
- return path.resolve(candidate);
56
- }
57
- }
58
-
59
- export function resolveCssImportPath(
60
- importingFilePath: string,
61
- specifier: string,
62
- projectRoot: string
63
- ): { resolvedPath: string; escapesProjectRoot: boolean } {
64
- const normalizedSpecifier = stripImportSuffix(specifier);
65
- const importingDir = path.dirname(importingFilePath);
66
- const rootCanonical = canonicalizePath(projectRoot);
67
-
68
- const unresolvedTarget = normalizedSpecifier.startsWith('/')
69
- ? path.join(rootCanonical, normalizedSpecifier.slice(1))
70
- : path.resolve(importingDir, normalizedSpecifier);
71
-
72
- const targetCanonical = canonicalizePath(unresolvedTarget);
73
- const relativeToRoot = path.relative(rootCanonical, targetCanonical);
74
- const escapesProjectRoot =
75
- relativeToRoot.startsWith('..') ||
76
- path.isAbsolute(relativeToRoot);
77
-
78
- return {
79
- resolvedPath: targetCanonical,
80
- escapesProjectRoot
81
- };
82
- }
83
-
84
- export function classifyZenithFile(filePath: string): ZenithFileKind {
85
- const normalized = filePath.replace(/\\/g, '/');
86
-
87
- if (!normalized.endsWith('.zen')) {
88
- return 'unknown';
89
- }
90
-
91
- if (normalized.includes('/src/pages/') || normalized.includes('/app/pages/')) {
92
- return 'page';
93
- }
94
-
95
- if (normalized.includes('/src/layouts/') || normalized.includes('/app/layouts/')) {
96
- return 'layout';
97
- }
98
-
99
- return 'component';
100
- }