@zenithbuild/language-server 0.2.2
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/.github/workflows/release.yml +254 -0
- package/.releaserc.json +73 -0
- package/CHANGELOG.md +37 -0
- package/LICENSE +21 -0
- package/README.md +32 -0
- package/bun.lock +83 -0
- package/package.json +34 -0
- package/scripts/release.ts +554 -0
- package/src/diagnostics.ts +260 -0
- package/src/imports.ts +207 -0
- package/src/metadata/core-imports.ts +163 -0
- package/src/metadata/directive-metadata.ts +109 -0
- package/src/metadata/plugin-imports.ts +116 -0
- package/src/project.ts +167 -0
- package/src/router.ts +180 -0
- package/src/server.ts +841 -0
- package/test/fixtures/content-plugin.zen +77 -0
- package/test/fixtures/core-only.zen +59 -0
- package/test/fixtures/no-plugins.zen +115 -0
- package/test/fixtures/router-enabled.zen +76 -0
- package/tsconfig.json +25 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Test Fixture: Content Plugin
|
|
3
|
+
|
|
4
|
+
This file tests Zenith features WITH content plugin imported.
|
|
5
|
+
The LSP should provide content-aware completions and soft warnings.
|
|
6
|
+
-->
|
|
7
|
+
<script>
|
|
8
|
+
import { zenEffect, zenOnMount } from 'zenith'
|
|
9
|
+
import { zenCollection, getCollection, getEntry } from 'zenith:content'
|
|
10
|
+
|
|
11
|
+
state posts = []
|
|
12
|
+
state currentPost = null
|
|
13
|
+
|
|
14
|
+
// Define a content collection
|
|
15
|
+
const blogCollection = zenCollection({
|
|
16
|
+
name: 'blog',
|
|
17
|
+
schema: {
|
|
18
|
+
title: 'string',
|
|
19
|
+
date: 'date',
|
|
20
|
+
author: 'string',
|
|
21
|
+
tags: 'array'
|
|
22
|
+
}
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
zenOnMount(async () => {
|
|
26
|
+
// Get all posts from the collection
|
|
27
|
+
posts = await getCollection('blog')
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
async function loadPost(slug) {
|
|
31
|
+
currentPost = await getEntry('blog', slug)
|
|
32
|
+
}
|
|
33
|
+
</script>
|
|
34
|
+
|
|
35
|
+
<div class="blog-list">
|
|
36
|
+
<h1>Blog Posts</h1>
|
|
37
|
+
|
|
38
|
+
<!-- List all posts -->
|
|
39
|
+
<ul zen:if="posts.length > 0">
|
|
40
|
+
<li zen:for="post in posts">
|
|
41
|
+
<h2>{post.data.title}</h2>
|
|
42
|
+
<p>By {post.data.author} on {post.data.date}</p>
|
|
43
|
+
<button onclick={() => loadPost(post.slug)}>Read More</button>
|
|
44
|
+
</li>
|
|
45
|
+
</ul>
|
|
46
|
+
|
|
47
|
+
<p zen:if="posts.length === 0">Loading posts...</p>
|
|
48
|
+
|
|
49
|
+
<!-- Current post display -->
|
|
50
|
+
<article zen:if="currentPost">
|
|
51
|
+
<h1>{currentPost.data.title}</h1>
|
|
52
|
+
<div class="meta">
|
|
53
|
+
<span>By {currentPost.data.author}</span>
|
|
54
|
+
<span>{currentPost.data.date}</span>
|
|
55
|
+
</div>
|
|
56
|
+
<div class="content">
|
|
57
|
+
{currentPost.content}
|
|
58
|
+
</div>
|
|
59
|
+
</article>
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
<style>
|
|
63
|
+
.blog-list {
|
|
64
|
+
max-width: 800px;
|
|
65
|
+
margin: 0 auto;
|
|
66
|
+
padding: 2rem;
|
|
67
|
+
}
|
|
68
|
+
article {
|
|
69
|
+
margin-top: 2rem;
|
|
70
|
+
padding-top: 2rem;
|
|
71
|
+
border-top: 1px solid #eee;
|
|
72
|
+
}
|
|
73
|
+
.meta {
|
|
74
|
+
color: #666;
|
|
75
|
+
margin-bottom: 1rem;
|
|
76
|
+
}
|
|
77
|
+
</style>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Test Fixture: Core Only
|
|
3
|
+
|
|
4
|
+
This file tests core Zenith features WITHOUT router or plugins.
|
|
5
|
+
The LSP should work correctly with just core functionality.
|
|
6
|
+
-->
|
|
7
|
+
<script>
|
|
8
|
+
import { zenEffect, zenOnMount } from 'zenith'
|
|
9
|
+
|
|
10
|
+
state count = 0
|
|
11
|
+
state message = "Hello, Zenith!"
|
|
12
|
+
|
|
13
|
+
function increment() {
|
|
14
|
+
count++
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
zenOnMount(() => {
|
|
18
|
+
console.log('Component mounted')
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
zenEffect(() => {
|
|
22
|
+
console.log('Count changed:', count)
|
|
23
|
+
})
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<div class="container">
|
|
27
|
+
<h1>{message}</h1>
|
|
28
|
+
|
|
29
|
+
<!-- zen:if directive -->
|
|
30
|
+
<p zen:if="count > 0">Count is positive: {count}</p>
|
|
31
|
+
|
|
32
|
+
<!-- zen:for directive -->
|
|
33
|
+
<ul>
|
|
34
|
+
<li zen:for="item in ['a', 'b', 'c']">{item}</li>
|
|
35
|
+
</ul>
|
|
36
|
+
|
|
37
|
+
<!-- zen:show directive -->
|
|
38
|
+
<span zen:show="count !== 0">Not zero!</span>
|
|
39
|
+
|
|
40
|
+
<!-- Event handlers -->
|
|
41
|
+
<button onclick={increment}>Increment</button>
|
|
42
|
+
<button @click={increment}>Also Increment</button>
|
|
43
|
+
|
|
44
|
+
<!-- Reactive bindings -->
|
|
45
|
+
<input :value="message" />
|
|
46
|
+
<div :class="count > 0 ? 'positive' : 'zero'">{count}</div>
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<style>
|
|
50
|
+
.container {
|
|
51
|
+
padding: 1rem;
|
|
52
|
+
}
|
|
53
|
+
.positive {
|
|
54
|
+
color: green;
|
|
55
|
+
}
|
|
56
|
+
.zero {
|
|
57
|
+
color: gray;
|
|
58
|
+
}
|
|
59
|
+
</style>
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Test Fixture: No Plugins
|
|
3
|
+
|
|
4
|
+
This file tests behavior when NO external imports are used.
|
|
5
|
+
The LSP should work with just vanilla Zenith features.
|
|
6
|
+
|
|
7
|
+
Validation rules:
|
|
8
|
+
- Removing router should not crash the IDE
|
|
9
|
+
- Missing plugin imports should show soft warnings
|
|
10
|
+
- Invalid syntax should be surfaced immediately
|
|
11
|
+
- IDE should never suggest unavailable APIs
|
|
12
|
+
-->
|
|
13
|
+
<script>
|
|
14
|
+
// No imports - just vanilla state and lifecycle
|
|
15
|
+
state items = ['Apple', 'Banana', 'Cherry']
|
|
16
|
+
state selectedIndex = 0
|
|
17
|
+
state filter = ''
|
|
18
|
+
|
|
19
|
+
function selectItem(index) {
|
|
20
|
+
selectedIndex = index
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function addItem() {
|
|
24
|
+
items = [...items, 'New Item']
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function removeItem(index) {
|
|
28
|
+
items = items.filter((_, i) => i !== index)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Computed-like pattern
|
|
32
|
+
function getFilteredItems() {
|
|
33
|
+
if (!filter) return items
|
|
34
|
+
return items.filter(item =>
|
|
35
|
+
item.toLowerCase().includes(filter.toLowerCase())
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
</script>
|
|
39
|
+
|
|
40
|
+
<div class="app">
|
|
41
|
+
<header>
|
|
42
|
+
<h1>Simple List App</h1>
|
|
43
|
+
<p>Selected: {items[selectedIndex] || 'None'}</p>
|
|
44
|
+
</header>
|
|
45
|
+
|
|
46
|
+
<div class="controls">
|
|
47
|
+
<input
|
|
48
|
+
type="text"
|
|
49
|
+
placeholder="Filter items..."
|
|
50
|
+
:value="filter"
|
|
51
|
+
oninput={(e) => filter = e.target.value}
|
|
52
|
+
/>
|
|
53
|
+
<button onclick={addItem}>Add Item</button>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<ul class="item-list">
|
|
57
|
+
<li
|
|
58
|
+
zen:for="item, index in getFilteredItems()"
|
|
59
|
+
:class="index === selectedIndex ? 'selected' : ''"
|
|
60
|
+
>
|
|
61
|
+
<span onclick={() => selectItem(index)}>{item}</span>
|
|
62
|
+
<button onclick={() => removeItem(index)}>Remove</button>
|
|
63
|
+
</li>
|
|
64
|
+
</ul>
|
|
65
|
+
|
|
66
|
+
<p zen:if="getFilteredItems().length === 0">
|
|
67
|
+
No items match the filter.
|
|
68
|
+
</p>
|
|
69
|
+
|
|
70
|
+
<footer>
|
|
71
|
+
<p>Total items: {items.length}</p>
|
|
72
|
+
<p zen:show="filter">Filtered: {getFilteredItems().length}</p>
|
|
73
|
+
</footer>
|
|
74
|
+
</div>
|
|
75
|
+
|
|
76
|
+
<style>
|
|
77
|
+
.app {
|
|
78
|
+
max-width: 600px;
|
|
79
|
+
margin: 0 auto;
|
|
80
|
+
padding: 1rem;
|
|
81
|
+
}
|
|
82
|
+
header {
|
|
83
|
+
margin-bottom: 1rem;
|
|
84
|
+
}
|
|
85
|
+
.controls {
|
|
86
|
+
display: flex;
|
|
87
|
+
gap: 0.5rem;
|
|
88
|
+
margin-bottom: 1rem;
|
|
89
|
+
}
|
|
90
|
+
.controls input {
|
|
91
|
+
flex: 1;
|
|
92
|
+
padding: 0.5rem;
|
|
93
|
+
}
|
|
94
|
+
.item-list {
|
|
95
|
+
list-style: none;
|
|
96
|
+
padding: 0;
|
|
97
|
+
}
|
|
98
|
+
.item-list li {
|
|
99
|
+
display: flex;
|
|
100
|
+
justify-content: space-between;
|
|
101
|
+
padding: 0.5rem;
|
|
102
|
+
border-bottom: 1px solid #eee;
|
|
103
|
+
cursor: pointer;
|
|
104
|
+
}
|
|
105
|
+
.item-list li.selected {
|
|
106
|
+
background: #e0f0ff;
|
|
107
|
+
}
|
|
108
|
+
.item-list li:hover {
|
|
109
|
+
background: #f5f5f5;
|
|
110
|
+
}
|
|
111
|
+
footer {
|
|
112
|
+
margin-top: 1rem;
|
|
113
|
+
color: #666;
|
|
114
|
+
}
|
|
115
|
+
</style>
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Test Fixture: Router Enabled
|
|
3
|
+
|
|
4
|
+
This file tests Zenith features WITH router imported.
|
|
5
|
+
The LSP should provide router-aware completions and hovers.
|
|
6
|
+
-->
|
|
7
|
+
<script>
|
|
8
|
+
import { zenEffect, zenOnMount } from 'zenith'
|
|
9
|
+
import { ZenLink, useRoute, useRouter, navigate, prefetch } from 'zenith/router'
|
|
10
|
+
|
|
11
|
+
state activeSection = 'home'
|
|
12
|
+
|
|
13
|
+
// Router hooks - should have hover info
|
|
14
|
+
const route = useRoute()
|
|
15
|
+
const router = useRouter()
|
|
16
|
+
|
|
17
|
+
zenOnMount(() => {
|
|
18
|
+
// Prefetch common routes
|
|
19
|
+
prefetch('/about')
|
|
20
|
+
prefetch('/blog')
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
zenEffect(() => {
|
|
24
|
+
// React to route changes
|
|
25
|
+
activeSection = route.path.split('/')[1] || 'home'
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
function goToAbout() {
|
|
29
|
+
navigate('/about')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function goBack() {
|
|
33
|
+
router.back()
|
|
34
|
+
}
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
<nav>
|
|
38
|
+
<!-- ZenLink components - should have special highlighting and props -->
|
|
39
|
+
<ZenLink to="/">Home</ZenLink>
|
|
40
|
+
<ZenLink to="/about" preload>About</ZenLink>
|
|
41
|
+
<ZenLink to="/blog" preload replace>Blog</ZenLink>
|
|
42
|
+
|
|
43
|
+
<!-- Dynamic route params -->
|
|
44
|
+
<ZenLink to="/blog/{route.params.slug}">Current Post</ZenLink>
|
|
45
|
+
</nav>
|
|
46
|
+
|
|
47
|
+
<main>
|
|
48
|
+
<h1>Current path: {route.path}</h1>
|
|
49
|
+
|
|
50
|
+
<!-- Route params and query -->
|
|
51
|
+
<p zen:if="route.params.id">Viewing item: {route.params.id}</p>
|
|
52
|
+
<p zen:if="route.query.search">Searching: {route.query.search}</p>
|
|
53
|
+
|
|
54
|
+
<div class="actions">
|
|
55
|
+
<button onclick={goToAbout}>Go to About</button>
|
|
56
|
+
<button onclick={goBack}>Go Back</button>
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
<!-- Slot for page content -->
|
|
60
|
+
<slot />
|
|
61
|
+
</main>
|
|
62
|
+
|
|
63
|
+
<style>
|
|
64
|
+
nav {
|
|
65
|
+
display: flex;
|
|
66
|
+
gap: 1rem;
|
|
67
|
+
padding: 1rem;
|
|
68
|
+
background: #f0f0f0;
|
|
69
|
+
}
|
|
70
|
+
main {
|
|
71
|
+
padding: 2rem;
|
|
72
|
+
}
|
|
73
|
+
.actions {
|
|
74
|
+
margin-top: 1rem;
|
|
75
|
+
}
|
|
76
|
+
</style>
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"lib": [
|
|
6
|
+
"ES2022"
|
|
7
|
+
],
|
|
8
|
+
"outDir": "./dist",
|
|
9
|
+
"rootDir": "./src",
|
|
10
|
+
"strict": true,
|
|
11
|
+
"esModuleInterop": true,
|
|
12
|
+
"skipLibCheck": true,
|
|
13
|
+
"forceConsistentCasingInFileNames": true,
|
|
14
|
+
"declaration": true,
|
|
15
|
+
"resolveJsonModule": true,
|
|
16
|
+
"moduleResolution": "node"
|
|
17
|
+
},
|
|
18
|
+
"include": [
|
|
19
|
+
"src/**/*"
|
|
20
|
+
],
|
|
21
|
+
"exclude": [
|
|
22
|
+
"node_modules",
|
|
23
|
+
"dist"
|
|
24
|
+
]
|
|
25
|
+
}
|