@tabnas/lsp 0.1.0
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/LICENSE +21 -0
- package/README.md +51 -0
- package/bin/tabnas-lsp-gen.js +98 -0
- package/bin/tabnas-lsp.js +7 -0
- package/data/diagnostic-fixtures.json +2888 -0
- package/data/registry.json +424 -0
- package/package.json +52 -0
- package/src/core.js +299 -0
- package/src/documents.js +97 -0
- package/src/generate.js +962 -0
- package/src/instances.js +141 -0
- package/src/loaders.js +515 -0
- package/src/registry.js +194 -0
- package/src/server.js +371 -0
- package/tools/gen-fixtures.js +90 -0
- package/tools/gen-registry.js +153 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/* Copyright (c) 2026 Richard Rodger, MIT License */
|
|
3
|
+
'use strict'
|
|
4
|
+
|
|
5
|
+
// Generate data/registry.json from fleet tabnas.plugin.json
|
|
6
|
+
// descriptors (the mcp/ts/tools/gen-data.js pattern: sibling
|
|
7
|
+
// checkouts, one and two levels up, dedupe by name, sort), merged with
|
|
8
|
+
// the overrides table below for the fields descriptors do not carry
|
|
9
|
+
// yet (plan C1). Each override is deleted as C1 lands per repo.
|
|
10
|
+
|
|
11
|
+
const fs = require('fs')
|
|
12
|
+
const path = require('path')
|
|
13
|
+
|
|
14
|
+
// languageId / kind / capability overrides (design §3). enabled:false
|
|
15
|
+
// = editor-collision policy default-off (entrenched incumbents).
|
|
16
|
+
const OVERRIDES = {
|
|
17
|
+
'@tabnas/json': { enabled: false },
|
|
18
|
+
'@tabnas/jsonc': { enabled: false },
|
|
19
|
+
'@tabnas/css': { enabled: false },
|
|
20
|
+
'@tabnas/markdown': { enabled: false, grammarKind: 'external' },
|
|
21
|
+
'@tabnas/yaml': { enabled: false },
|
|
22
|
+
'@tabnas/c': { enabled: false, grammarKind: 'external' },
|
|
23
|
+
'@tabnas/toml': { enabled: false },
|
|
24
|
+
'@tabnas/xml': { enabled: false },
|
|
25
|
+
'@tabnas/ini': { enabled: false },
|
|
26
|
+
'@tabnas/proto': { enabled: false, grammarKind: 'compiled' },
|
|
27
|
+
'@tabnas/chess': { languageId: 'pgn' },
|
|
28
|
+
'@tabnas/csv': {},
|
|
29
|
+
'@tabnas/json5': {},
|
|
30
|
+
'@tabnas/jsonl': {},
|
|
31
|
+
'@tabnas/zon': {},
|
|
32
|
+
'@tabnas/feed': { pluginKind: 'grammar' },
|
|
33
|
+
'@tabnas/abnf': { pluginKind: 'compiler' },
|
|
34
|
+
'@tabnas/ebnf': { pluginKind: 'compiler' },
|
|
35
|
+
'@tabnas/gbnf': { pluginKind: 'compiler' },
|
|
36
|
+
'@tabnas/bnf': { pluginKind: 'compiler' },
|
|
37
|
+
'@tabnas/expr': { pluginKind: 'modifier', grammarKind: 'imperative' },
|
|
38
|
+
'@tabnas/hoover': { pluginKind: 'modifier', grammarKind: 'imperative' },
|
|
39
|
+
'@tabnas/debug': { pluginKind: 'modifier', grammarKind: 'imperative' },
|
|
40
|
+
'@tabnas/directive': { pluginKind: 'modifier' },
|
|
41
|
+
'@tabnas/multisource': { pluginKind: 'modifier' },
|
|
42
|
+
'@tabnas/path': { pluginKind: 'modifier' },
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function findDescriptors(root) {
|
|
46
|
+
const found = new Map()
|
|
47
|
+
const levels = [root, path.join(root, '..')]
|
|
48
|
+
for (const base of levels) {
|
|
49
|
+
let names = []
|
|
50
|
+
try {
|
|
51
|
+
names = fs.readdirSync(base)
|
|
52
|
+
} catch (e) {
|
|
53
|
+
continue
|
|
54
|
+
}
|
|
55
|
+
for (const n of names) {
|
|
56
|
+
const p = path.join(base, n, 'tabnas.plugin.json')
|
|
57
|
+
try {
|
|
58
|
+
const d = JSON.parse(fs.readFileSync(p, 'utf8'))
|
|
59
|
+
if (d.name && !found.has(d.name)) found.set(d.name, d)
|
|
60
|
+
} catch (e) {
|
|
61
|
+
// not a plugin repo
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return [...found.values()].sort((a, b) => a.name.localeCompare(b.name))
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Fields a descriptor may carry once plan C1 has landed for its repo
|
|
69
|
+
// (tasks/ax-descriptor.sh preserves exactly these through
|
|
70
|
+
// regeneration). A descriptor that carries one is AUTHORITATIVE for it:
|
|
71
|
+
// the override table is interim scaffolding, and ADR-10 says derive,
|
|
72
|
+
// never duplicate. Without this the table would silently shadow the
|
|
73
|
+
// descriptor the moment C1 rolled out, which is the failure that would
|
|
74
|
+
// look like C1 having no effect.
|
|
75
|
+
const LSP_FIELDS = [
|
|
76
|
+
'languageId',
|
|
77
|
+
'grammarKind',
|
|
78
|
+
'pluginKind',
|
|
79
|
+
'syncGroups',
|
|
80
|
+
'semanticTokens',
|
|
81
|
+
'lexStream',
|
|
82
|
+
'optionsSchema',
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
// `enabled` is deliberately NOT in that list. It encodes the editor
|
|
86
|
+
// collision policy — whether tabnas should claim a language an
|
|
87
|
+
// entrenched extension already owns — which is a host decision, not a
|
|
88
|
+
// fact about the grammar. A plugin must not be able to enable itself.
|
|
89
|
+
|
|
90
|
+
function entryFor(d, redundant) {
|
|
91
|
+
const out = {
|
|
92
|
+
name: d.name,
|
|
93
|
+
extensions: d.extensions || [],
|
|
94
|
+
mediaTypes: d.mediaTypes || [],
|
|
95
|
+
base: d.base || null,
|
|
96
|
+
errorCodes: d.errorCodes || [],
|
|
97
|
+
}
|
|
98
|
+
const ov = OVERRIDES[d.name] || {}
|
|
99
|
+
for (const k of Object.keys(ov)) out[k] = ov[k]
|
|
100
|
+
for (const f of LSP_FIELDS) {
|
|
101
|
+
if (undefined === d[f]) continue
|
|
102
|
+
if (f in ov) {
|
|
103
|
+
redundant.push(d.name + '.' + f)
|
|
104
|
+
}
|
|
105
|
+
out[f] = d[f]
|
|
106
|
+
}
|
|
107
|
+
return out
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function main() {
|
|
111
|
+
// Default to the promoted layout — this package is the `ts/` half of
|
|
112
|
+
// the `lsp` repo, sitting beside its sibling repos in a fleet
|
|
113
|
+
// checkout, so the fleet root is two levels above the package
|
|
114
|
+
// (ts/tools -> ts -> lsp -> fleet). On a lone checkout with no
|
|
115
|
+
// sibling repos the guard below makes that a clear error rather than
|
|
116
|
+
// an empty registry.
|
|
117
|
+
const root = process.argv[2] || path.join(__dirname, '..', '..', '..')
|
|
118
|
+
const descriptors = findDescriptors(root)
|
|
119
|
+
|
|
120
|
+
// Finding nothing means the fleet is not where we looked, not that
|
|
121
|
+
// the fleet is empty — a wrong root is the likely cause, and this
|
|
122
|
+
// staging layout is not where the promoted repo will sit. Writing
|
|
123
|
+
// the empty result would replace a good registry with one that
|
|
124
|
+
// routes no language at all, and the server would then serve
|
|
125
|
+
// nothing while reporting no error. Refuse instead.
|
|
126
|
+
if (0 === descriptors.length) {
|
|
127
|
+
console.error(
|
|
128
|
+
'gen-registry: no tabnas.plugin.json found under ' + root + '\n' +
|
|
129
|
+
'Pass the fleet root explicitly: node tools/gen-registry.js <root>\n' +
|
|
130
|
+
'Refusing to overwrite data/registry.json with an empty registry.',
|
|
131
|
+
)
|
|
132
|
+
process.exitCode = 1
|
|
133
|
+
return
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const redundant = []
|
|
137
|
+
const entries = descriptors.map((d) => entryFor(d, redundant))
|
|
138
|
+
const out = { generated: 'tools/gen-registry.js', count: entries.length, entries }
|
|
139
|
+
const file = path.join(__dirname, '..', 'data', 'registry.json')
|
|
140
|
+
fs.writeFileSync(file, JSON.stringify(out, null, 2) + '\n')
|
|
141
|
+
console.log('wrote ' + file + ' (' + entries.length + ' entries)')
|
|
142
|
+
// Naming them is how the interim table actually gets deleted rather
|
|
143
|
+
// than quietly outliving the migration it was meant to bridge.
|
|
144
|
+
if (0 < redundant.length) {
|
|
145
|
+
console.log(
|
|
146
|
+
'descriptors now carry these — delete from OVERRIDES: ' +
|
|
147
|
+
redundant.join(', '),
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (require.main === module) main()
|
|
153
|
+
module.exports = { findDescriptors, entryFor, OVERRIDES, LSP_FIELDS }
|