@zenithbuild/language-server 0.2.7 → 0.5.0-beta.2.1

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,81 +1,392 @@
1
1
  /**
2
2
  * Diagnostics
3
- *
4
- * Compile-time validation mirroring the Zenith compiler.
5
- * The LSP must surface compiler-level errors early.
6
- *
7
- * Important: No runtime execution. Pure static analysis only.
3
+ *
4
+ * Compile-time validation mirroring Zenith contracts.
5
+ * No runtime execution. Pure static analysis only.
8
6
  */
9
7
 
10
- import { Diagnostic, DiagnosticSeverity } from 'vscode-languageserver/node';
11
- import { TextDocument } from 'vscode-languageserver-textdocument';
12
- import { isDirective, parseForExpression } from './metadata/directive-metadata';
8
+ import * as path from 'path';
9
+
10
+ import { parseForExpression } from './metadata/directive-metadata';
13
11
  import { parseZenithImports, resolveModule, isPluginModule } from './imports';
14
12
  import type { ProjectGraph } from './project';
13
+ import {
14
+ classifyZenithFile,
15
+ isCssContractImportSpecifier,
16
+ isLocalCssSpecifier,
17
+ resolveCssImportPath
18
+ } from './contracts';
19
+ import type { ZenithServerSettings } from './settings';
20
+ import { EVENT_BINDING_DIAGNOSTIC_CODE } from './code-actions';
21
+
22
+ const COMPONENT_SCRIPT_CONTRACT_MESSAGE =
23
+ 'Zenith Contract Violation: Components are structural; move <script> to the parent route scope.';
24
+
25
+ const CSS_BARE_IMPORT_MESSAGE =
26
+ 'CSS import contract violation: bare CSS imports are not supported.';
27
+
28
+ const CSS_ESCAPE_MESSAGE =
29
+ 'CSS import contract violation: imported CSS path escapes project root.';
30
+
31
+ const DiagnosticSeverity = {
32
+ Error: 1,
33
+ Warning: 2,
34
+ Information: 3,
35
+ Hint: 4
36
+ } as const;
37
+
38
+ export interface ZenithPosition {
39
+ line: number;
40
+ character: number;
41
+ }
42
+
43
+ export interface ZenithRange {
44
+ start: ZenithPosition;
45
+ end: ZenithPosition;
46
+ }
47
+
48
+ export interface ZenithDiagnostic {
49
+ severity: number;
50
+ range: ZenithRange;
51
+ message: string;
52
+ source: string;
53
+ code?: string;
54
+ data?: unknown;
55
+ }
56
+
57
+ export interface ZenithTextDocumentLike {
58
+ uri: string;
59
+ getText(): string;
60
+ positionAt(offset: number): ZenithPosition;
61
+ }
62
+
63
+ function uriToFilePath(uri: string): string {
64
+ try {
65
+ return decodeURIComponent(new URL(uri).pathname);
66
+ } catch {
67
+ return decodeURIComponent(uri.replace('file://', ''));
68
+ }
69
+ }
70
+
71
+ function stripScriptAndStylePreserveIndices(text: string): string {
72
+ return text.replace(/<(script|style)\b[^>]*>[\s\S]*?<\/\1>/gi, (match) => ' '.repeat(match.length));
73
+ }
74
+
75
+ interface ScriptBlock {
76
+ content: string;
77
+ contentStartOffset: number;
78
+ }
79
+
80
+ function getScriptBlocks(text: string): ScriptBlock[] {
81
+ const blocks: ScriptBlock[] = [];
82
+ const scriptPattern = /<script\b[^>]*>([\s\S]*?)<\/script>/gi;
83
+ let match: RegExpExecArray | null;
84
+
85
+ while ((match = scriptPattern.exec(text)) !== null) {
86
+ const whole = match[0] || '';
87
+ const content = match[1] || '';
88
+ const localStart = whole.indexOf(content);
89
+ const contentStartOffset = (match.index || 0) + Math.max(localStart, 0);
90
+ blocks.push({ content, contentStartOffset });
91
+ }
92
+
93
+ return blocks;
94
+ }
95
+
96
+ interface ParsedImportSpecifier {
97
+ specifier: string;
98
+ startOffset: number;
99
+ endOffset: number;
100
+ }
101
+
102
+ function parseImportSpecifiers(scriptContent: string, scriptStartOffset: number): ParsedImportSpecifier[] {
103
+ const imports: ParsedImportSpecifier[] = [];
104
+ const importPattern = /import\s+(?:[^'";]+?\s+from\s+)?['"]([^'"\n]+)['"]/g;
105
+ let match: RegExpExecArray | null;
106
+
107
+ while ((match = importPattern.exec(scriptContent)) !== null) {
108
+ const statement = match[0] || '';
109
+ const specifier = match[1] || '';
110
+ const rel = statement.indexOf(specifier);
111
+ const startOffset = scriptStartOffset + (match.index || 0) + Math.max(rel, 0);
112
+ const endOffset = startOffset + specifier.length;
113
+ imports.push({ specifier, startOffset, endOffset });
114
+ }
115
+
116
+ return imports;
117
+ }
118
+
119
+ function normalizeEventHandlerValue(rawValue: string): string {
120
+ let value = rawValue.trim();
121
+
122
+ if ((value.startsWith('{') && value.endsWith('}')) ||
123
+ (value.startsWith('"') && value.endsWith('"')) ||
124
+ (value.startsWith("'") && value.endsWith("'"))) {
125
+ value = value.slice(1, -1).trim();
126
+ }
127
+
128
+ if (/^[a-zA-Z_$][a-zA-Z0-9_$]*\(\)$/.test(value)) {
129
+ value = value.slice(0, -2);
130
+ }
131
+
132
+ if (!value) {
133
+ return 'handler';
134
+ }
15
135
 
16
- export interface ValidationContext {
17
- document: TextDocument;
18
- text: string;
19
- graph: ProjectGraph | null;
136
+ return value;
20
137
  }
21
138
 
22
139
  /**
23
- * Collect all diagnostics for a document
140
+ * Collect all diagnostics for a document.
24
141
  */
25
- export function collectDiagnostics(document: TextDocument, graph: ProjectGraph | null): Diagnostic[] {
26
- const diagnostics: Diagnostic[] = [];
142
+ export async function collectDiagnostics(
143
+ document: ZenithTextDocumentLike,
144
+ graph: ProjectGraph | null,
145
+ settings: ZenithServerSettings,
146
+ projectRoot: string | null
147
+ ): Promise<ZenithDiagnostic[]> {
148
+ const diagnostics: ZenithDiagnostic[] = [];
27
149
  const text = document.getText();
150
+ const filePath = uriToFilePath(document.uri);
28
151
 
29
- // Validate component references
30
- collectComponentDiagnostics(document, text, graph, diagnostics);
152
+ let hasComponentScriptCompilerDiagnostic = false;
31
153
 
32
- // Validate directive usage
33
- collectDirectiveDiagnostics(document, text, diagnostics);
154
+ // 1) Compiler validation (source-of-truth), with configurable suppression for component script contract.
155
+ try {
156
+ process.env.ZENITH_CACHE = '1';
157
+ const { compile } = await import('@zenithbuild/compiler');
158
+ await compile(text, filePath);
159
+ } catch (error: any) {
160
+ const message = String(error?.message || 'Unknown compiler error');
161
+ const isContractViolation = message.includes(COMPONENT_SCRIPT_CONTRACT_MESSAGE);
34
162
 
35
- // Validate imports
36
- collectImportDiagnostics(document, text, diagnostics);
163
+ if (isContractViolation) {
164
+ hasComponentScriptCompilerDiagnostic = true;
165
+ }
166
+
167
+ if (!(settings.componentScripts === 'allow' && isContractViolation)) {
168
+ diagnostics.push({
169
+ severity: DiagnosticSeverity.Error,
170
+ range: {
171
+ start: { line: (error?.line || 1) - 1, character: (error?.column || 1) - 1 },
172
+ end: { line: (error?.line || 1) - 1, character: (error?.column || 1) + 20 }
173
+ },
174
+ message: `[${error?.code || 'compiler'}] ${message}${error?.hints ? '\n\nHints:\n' + error.hints.join('\n') : ''}`,
175
+ source: 'zenith-compiler'
176
+ });
177
+ }
178
+ }
179
+
180
+ diagnostics.push(
181
+ ...collectContractDiagnostics(
182
+ document,
183
+ graph,
184
+ settings,
185
+ projectRoot,
186
+ hasComponentScriptCompilerDiagnostic
187
+ )
188
+ );
37
189
 
38
- // Validate expressions
190
+ return diagnostics;
191
+ }
192
+
193
+ export function collectContractDiagnostics(
194
+ document: ZenithTextDocumentLike,
195
+ graph: ProjectGraph | null,
196
+ settings: ZenithServerSettings,
197
+ projectRoot: string | null,
198
+ hasComponentScriptCompilerDiagnostic = false
199
+ ): ZenithDiagnostic[] {
200
+ const diagnostics: ZenithDiagnostic[] = [];
201
+ const text = document.getText();
202
+ const filePath = uriToFilePath(document.uri);
203
+
204
+ collectComponentScriptDiagnostics(document, text, filePath, settings, diagnostics, hasComponentScriptCompilerDiagnostic);
205
+ collectEventBindingDiagnostics(document, text, diagnostics);
206
+ collectDirectiveDiagnostics(document, text, diagnostics);
207
+ collectImportDiagnostics(document, text, diagnostics);
208
+ collectCssImportContractDiagnostics(document, text, filePath, projectRoot, diagnostics);
39
209
  collectExpressionDiagnostics(document, text, diagnostics);
210
+ collectComponentDiagnostics(document, text, graph, diagnostics);
40
211
 
41
212
  return diagnostics;
42
213
  }
43
214
 
215
+ function collectComponentScriptDiagnostics(
216
+ document: ZenithTextDocumentLike,
217
+ text: string,
218
+ filePath: string,
219
+ settings: ZenithServerSettings,
220
+ diagnostics: ZenithDiagnostic[],
221
+ hasComponentScriptCompilerDiagnostic: boolean
222
+ ): void {
223
+ if (settings.componentScripts !== 'forbid') {
224
+ return;
225
+ }
226
+
227
+ if (classifyZenithFile(filePath) !== 'component') {
228
+ return;
229
+ }
230
+
231
+ if (hasComponentScriptCompilerDiagnostic) {
232
+ return;
233
+ }
234
+
235
+ const scriptTagMatch = /<script\b[^>]*>/i.exec(text);
236
+ if (!scriptTagMatch || scriptTagMatch.index == null) {
237
+ return;
238
+ }
239
+
240
+ diagnostics.push({
241
+ severity: DiagnosticSeverity.Error,
242
+ range: {
243
+ start: document.positionAt(scriptTagMatch.index),
244
+ end: document.positionAt(scriptTagMatch.index + scriptTagMatch[0].length)
245
+ },
246
+ message: COMPONENT_SCRIPT_CONTRACT_MESSAGE,
247
+ source: 'zenith-contract'
248
+ });
249
+ }
250
+
251
+ function collectEventBindingDiagnostics(
252
+ document: ZenithTextDocumentLike,
253
+ text: string,
254
+ diagnostics: ZenithDiagnostic[]
255
+ ): void {
256
+ const stripped = stripScriptAndStylePreserveIndices(text);
257
+
258
+ // Invalid @click={handler}
259
+ const atEventPattern = /@([a-zA-Z][a-zA-Z0-9_-]*)\s*=\s*(\{[^}]*\}|"[^"]*"|'[^']*')/g;
260
+ let match: RegExpExecArray | null;
261
+
262
+ while ((match = atEventPattern.exec(stripped)) !== null) {
263
+ const fullMatch = match[0] || '';
264
+ const eventName = match[1] || 'click';
265
+ const rawHandler = match[2] || '{handler}';
266
+ const handler = normalizeEventHandlerValue(rawHandler);
267
+ const replacement = `on:${eventName}={${handler}}`;
268
+
269
+ diagnostics.push({
270
+ severity: DiagnosticSeverity.Error,
271
+ range: {
272
+ start: document.positionAt(match.index || 0),
273
+ end: document.positionAt((match.index || 0) + fullMatch.length)
274
+ },
275
+ message: `Invalid event binding syntax. Use on:${eventName}={handler}.`,
276
+ source: 'zenith-contract',
277
+ code: EVENT_BINDING_DIAGNOSTIC_CODE,
278
+ data: {
279
+ replacement,
280
+ title: `Convert to ${replacement}`
281
+ }
282
+ });
283
+ }
284
+
285
+ // Invalid onclick="handler" / onclick={handler}
286
+ const onEventPattern = /\bon([a-zA-Z][a-zA-Z0-9_-]*)\s*=\s*(\{[^}]*\}|"[^"]*"|'[^']*')/g;
287
+ while ((match = onEventPattern.exec(stripped)) !== null) {
288
+ const fullMatch = match[0] || '';
289
+ const eventName = match[1] || 'click';
290
+ const rawHandler = match[2] || '{handler}';
291
+ const handler = normalizeEventHandlerValue(rawHandler);
292
+ const replacement = `on:${eventName}={${handler}}`;
293
+
294
+ diagnostics.push({
295
+ severity: DiagnosticSeverity.Error,
296
+ range: {
297
+ start: document.positionAt(match.index || 0),
298
+ end: document.positionAt((match.index || 0) + fullMatch.length)
299
+ },
300
+ message: `Invalid event binding syntax. Use on:${eventName}={handler}.`,
301
+ source: 'zenith-contract',
302
+ code: EVENT_BINDING_DIAGNOSTIC_CODE,
303
+ data: {
304
+ replacement,
305
+ title: `Convert to ${replacement}`
306
+ }
307
+ });
308
+ }
309
+ }
310
+
311
+ function collectCssImportContractDiagnostics(
312
+ document: ZenithTextDocumentLike,
313
+ text: string,
314
+ filePath: string,
315
+ projectRoot: string | null,
316
+ diagnostics: ZenithDiagnostic[]
317
+ ): void {
318
+ const scriptBlocks = getScriptBlocks(text);
319
+ if (scriptBlocks.length === 0) {
320
+ return;
321
+ }
322
+
323
+ const effectiveProjectRoot = projectRoot ? path.resolve(projectRoot) : path.dirname(filePath);
324
+
325
+ for (const block of scriptBlocks) {
326
+ const imports = parseImportSpecifiers(block.content, block.contentStartOffset);
327
+ for (const imp of imports) {
328
+ if (!isCssContractImportSpecifier(imp.specifier)) {
329
+ continue;
330
+ }
331
+
332
+ if (!isLocalCssSpecifier(imp.specifier)) {
333
+ diagnostics.push({
334
+ severity: DiagnosticSeverity.Error,
335
+ range: {
336
+ start: document.positionAt(imp.startOffset),
337
+ end: document.positionAt(imp.endOffset)
338
+ },
339
+ message: CSS_BARE_IMPORT_MESSAGE,
340
+ source: 'zenith-contract'
341
+ });
342
+ continue;
343
+ }
344
+
345
+ const resolved = resolveCssImportPath(filePath, imp.specifier, effectiveProjectRoot);
346
+ if (resolved.escapesProjectRoot) {
347
+ diagnostics.push({
348
+ severity: DiagnosticSeverity.Error,
349
+ range: {
350
+ start: document.positionAt(imp.startOffset),
351
+ end: document.positionAt(imp.endOffset)
352
+ },
353
+ message: CSS_ESCAPE_MESSAGE,
354
+ source: 'zenith-contract'
355
+ });
356
+ }
357
+ }
358
+ }
359
+ }
360
+
44
361
  /**
45
- * Validate component references
362
+ * Validate component references.
46
363
  */
47
364
  function collectComponentDiagnostics(
48
- document: TextDocument,
365
+ document: ZenithTextDocumentLike,
49
366
  text: string,
50
367
  graph: ProjectGraph | null,
51
- diagnostics: Diagnostic[]
368
+ diagnostics: ZenithDiagnostic[]
52
369
  ): void {
53
370
  if (!graph) return;
54
371
 
55
- // Strip script and style blocks to avoid matching PascalCase names in TS generics
56
- // We use a simplified version that preserves indices by replacing content with spaces
57
372
  const strippedText = text
58
- .replace(/<(script|style)[^>]*>([\s\S]*?)<\/\1>/gi, (match, tag, content) => {
373
+ .replace(/<(script|style)[^>]*>([\s\S]*?)<\/\1>/gi, (match, _tag, content) => {
59
374
  return match.replace(content, ' '.repeat(content.length));
60
375
  });
61
376
 
62
- // Match component tags (PascalCase)
63
377
  const componentPattern = /<([A-Z][a-zA-Z0-9]*)(?=[\s/>])/g;
64
- let match;
378
+ let match: RegExpExecArray | null;
65
379
 
66
380
  while ((match = componentPattern.exec(strippedText)) !== null) {
67
381
  const componentName = match[1];
68
-
69
- // Skip known router components
70
382
  if (componentName === 'ZenLink') continue;
71
383
 
72
- // Check if component exists in project graph
73
384
  const inLayouts = graph.layouts.has(componentName);
74
385
  const inComponents = graph.components.has(componentName);
75
386
 
76
387
  if (!inLayouts && !inComponents) {
77
- const startPos = document.positionAt(match.index + 1);
78
- const endPos = document.positionAt(match.index + 1 + componentName.length);
388
+ const startPos = document.positionAt((match.index || 0) + 1);
389
+ const endPos = document.positionAt((match.index || 0) + 1 + componentName.length);
79
390
 
80
391
  diagnostics.push({
81
392
  severity: DiagnosticSeverity.Warning,
@@ -88,41 +399,38 @@ function collectComponentDiagnostics(
88
399
  }
89
400
 
90
401
  /**
91
- * Validate directive usage
402
+ * Validate directive usage.
92
403
  */
93
404
  function collectDirectiveDiagnostics(
94
- document: TextDocument,
405
+ document: ZenithTextDocumentLike,
95
406
  text: string,
96
- diagnostics: Diagnostic[]
407
+ diagnostics: ZenithDiagnostic[]
97
408
  ): void {
98
- // Match zen:* directives
99
409
  const directivePattern = /(zen:(?:if|for|effect|show))\s*=\s*["']([^"']*)["']/g;
100
- let match;
410
+ let match: RegExpExecArray | null;
101
411
 
102
412
  while ((match = directivePattern.exec(text)) !== null) {
103
413
  const directiveName = match[1];
104
414
  const directiveValue = match[2];
105
415
 
106
- // Validate zen:for syntax
107
416
  if (directiveName === 'zen:for') {
108
417
  const parsed = parseForExpression(directiveValue);
109
418
  if (!parsed) {
110
- const startPos = document.positionAt(match.index);
111
- const endPos = document.positionAt(match.index + match[0].length);
419
+ const startPos = document.positionAt(match.index || 0);
420
+ const endPos = document.positionAt((match.index || 0) + (match[0] || '').length);
112
421
 
113
422
  diagnostics.push({
114
423
  severity: DiagnosticSeverity.Error,
115
424
  range: { start: startPos, end: endPos },
116
- message: `Invalid zen:for syntax. Expected: "item in items" or "item, index in items"`,
425
+ message: 'Invalid zen:for syntax. Expected: "item in items" or "item, index in items"',
117
426
  source: 'zenith'
118
427
  });
119
428
  }
120
429
  }
121
430
 
122
- // Check for empty directive values
123
431
  if (!directiveValue.trim()) {
124
- const startPos = document.positionAt(match.index);
125
- const endPos = document.positionAt(match.index + match[0].length);
432
+ const startPos = document.positionAt(match.index || 0);
433
+ const endPos = document.positionAt((match.index || 0) + (match[0] || '').length);
126
434
 
127
435
  diagnostics.push({
128
436
  severity: DiagnosticSeverity.Error,
@@ -133,45 +441,40 @@ function collectDirectiveDiagnostics(
133
441
  }
134
442
  }
135
443
 
136
- // Check for zen:for on slot elements (forbidden)
137
444
  const slotForPattern = /<slot[^>]*zen:for/g;
138
445
  while ((match = slotForPattern.exec(text)) !== null) {
139
- const startPos = document.positionAt(match.index);
140
- const endPos = document.positionAt(match.index + match[0].length);
446
+ const startPos = document.positionAt(match.index || 0);
447
+ const endPos = document.positionAt((match.index || 0) + (match[0] || '').length);
141
448
 
142
449
  diagnostics.push({
143
450
  severity: DiagnosticSeverity.Error,
144
451
  range: { start: startPos, end: endPos },
145
- message: `zen:for cannot be used on <slot> elements`,
452
+ message: 'zen:for cannot be used on <slot> elements',
146
453
  source: 'zenith'
147
454
  });
148
455
  }
149
456
  }
150
457
 
151
458
  /**
152
- * Validate imports
459
+ * Validate imports.
153
460
  */
154
461
  function collectImportDiagnostics(
155
- document: TextDocument,
462
+ document: ZenithTextDocumentLike,
156
463
  text: string,
157
- diagnostics: Diagnostic[]
464
+ diagnostics: ZenithDiagnostic[]
158
465
  ): void {
159
- // Extract script content
160
466
  const scriptMatch = text.match(/<script[^>]*>([\s\S]*?)<\/script>/i);
161
467
  if (!scriptMatch) return;
162
468
 
163
469
  const scriptContent = scriptMatch[1];
164
- const scriptStart = scriptMatch.index! + scriptMatch[0].indexOf(scriptContent);
165
-
470
+ const scriptStart = (scriptMatch.index || 0) + scriptMatch[0].indexOf(scriptContent);
166
471
  const imports = parseZenithImports(scriptContent);
167
472
 
168
473
  for (const imp of imports) {
169
474
  const resolved = resolveModule(imp.module);
170
475
 
171
- // Warn about unknown plugin modules (soft diagnostic)
172
476
  if (isPluginModule(imp.module) && !resolved.isKnown) {
173
- // Find the import line in the document
174
- const importPattern = new RegExp(`import[^'"]*['"]${imp.module.replace(':', '\\:')}['"]`);
477
+ const importPattern = new RegExp(`import[^'\"]*['\"]${imp.module.replace(':', '\\:')}['\"]`);
175
478
  const importMatch = scriptContent.match(importPattern);
176
479
 
177
480
  if (importMatch) {
@@ -188,9 +491,8 @@ function collectImportDiagnostics(
188
491
  }
189
492
  }
190
493
 
191
- // Check for invalid specifiers in known modules
192
494
  if (resolved.isKnown && resolved.metadata) {
193
- const validExports = resolved.metadata.exports.map(e => e.name);
495
+ const validExports = resolved.metadata.exports.map((e) => e.name);
194
496
 
195
497
  for (const specifier of imp.specifiers) {
196
498
  if (!validExports.includes(specifier)) {
@@ -216,45 +518,60 @@ function collectImportDiagnostics(
216
518
  }
217
519
 
218
520
  /**
219
- * Validate expressions for dangerous patterns
521
+ * Validate expressions for dangerous patterns.
220
522
  */
221
523
  function collectExpressionDiagnostics(
222
- document: TextDocument,
524
+ document: ZenithTextDocumentLike,
223
525
  text: string,
224
- diagnostics: Diagnostic[]
526
+ diagnostics: ZenithDiagnostic[]
225
527
  ): void {
226
- // Match expressions in templates
227
528
  const expressionPattern = /\{([^}]+)\}/g;
228
- let match;
529
+ let match: RegExpExecArray | null;
229
530
 
230
531
  while ((match = expressionPattern.exec(text)) !== null) {
231
532
  const expression = match[1];
232
- const offset = match.index;
533
+ const offset = match.index || 0;
233
534
 
234
- // Check for dangerous patterns
235
535
  if (expression.includes('eval(') || expression.includes('Function(')) {
236
536
  const startPos = document.positionAt(offset);
237
- const endPos = document.positionAt(offset + match[0].length);
537
+ const endPos = document.positionAt(offset + (match[0] || '').length);
238
538
 
239
539
  diagnostics.push({
240
540
  severity: DiagnosticSeverity.Error,
241
541
  range: { start: startPos, end: endPos },
242
- message: `Dangerous pattern detected: eval() and Function() are not allowed in expressions`,
542
+ message: 'Dangerous pattern detected: eval() and Function() are not allowed in expressions',
243
543
  source: 'zenith'
244
544
  });
245
545
  }
246
546
 
247
- // Check for with statement
248
547
  if (/\bwith\s*\(/.test(expression)) {
249
548
  const startPos = document.positionAt(offset);
250
- const endPos = document.positionAt(offset + match[0].length);
549
+ const endPos = document.positionAt(offset + (match[0] || '').length);
550
+
551
+ diagnostics.push({
552
+ severity: DiagnosticSeverity.Error,
553
+ range: { start: startPos, end: endPos },
554
+ message: "'with' statement is not allowed in expressions",
555
+ source: 'zenith'
556
+ });
557
+ }
558
+
559
+ if (expression.includes(' as ') || (expression.includes('<') && expression.includes('>'))) {
560
+ const startPos = document.positionAt(offset);
561
+ const endPos = document.positionAt(offset + (match[0] || '').length);
251
562
 
252
563
  diagnostics.push({
253
564
  severity: DiagnosticSeverity.Error,
254
565
  range: { start: startPos, end: endPos },
255
- message: `'with' statement is not allowed in expressions`,
566
+ message: 'TypeScript syntax (type casting or generics) detected in runtime expression. Runtime code must be pure JavaScript.',
256
567
  source: 'zenith'
257
568
  });
258
569
  }
259
570
  }
260
571
  }
572
+
573
+ export const CONTRACT_MESSAGES = {
574
+ componentScript: COMPONENT_SCRIPT_CONTRACT_MESSAGE,
575
+ cssBareImport: CSS_BARE_IMPORT_MESSAGE,
576
+ cssEscape: CSS_ESCAPE_MESSAGE
577
+ } as const;