hivecode-mcp 0.8.0 → 0.11.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.
- package/README.md +116 -68
- package/core.js +62 -13
- package/hive-mcp.js +489 -50
- package/icr.js +502 -35
- package/lang-brace.js +240 -174
- package/lang-js.js +642 -418
- package/lang-json.js +184 -0
- package/lang-python.js +360 -149
- package/lang-ruby.js +230 -0
- package/lang-toml.js +136 -0
- package/lang-yaml.js +151 -0
- package/package.json +97 -70
- package/sync.js +413 -12
- package/MCP.md +0 -77
package/lang-brace.js
CHANGED
|
@@ -1,174 +1,240 @@
|
|
|
1
|
-
// Brace-family language provider for ICR — a dependency-free, language-agnostic
|
|
2
|
-
// structural splitter for C-style languages (TypeScript, Go, Rust, Java, C/C++,
|
|
3
|
-
// C#, Swift, Kotlin, Scala, PHP, Dart, …). It implements the ICR provider contract
|
|
4
|
-
// { id, exts, parses, units, declaredNames, splitUnit, parsesUnit } so the SAME
|
|
5
|
-
// merge engine in icr.js merges these languages by structure — different top-level
|
|
6
|
-
// declarations merge cleanly, the same declaration edited two ways is a named
|
|
7
|
-
// conflict, and edits to different members inside one class/function merge via the
|
|
8
|
-
// engine's finer-grained inner merge (splitUnit).
|
|
9
|
-
//
|
|
10
|
-
// It is a heuristic scanner, not a full parser: it tracks strings, line/block
|
|
11
|
-
// comments, and bracket depth to find top-level declarations and their byte ranges.
|
|
12
|
-
// This gives real structural merge and an approximate parses() (balanced delimiters)
|
|
13
|
-
// — honest and useful today; a tree-sitter provider can later replace it behind the
|
|
14
|
-
// same interface with zero engine changes.
|
|
15
|
-
|
|
16
|
-
const LINE = ['//']
|
|
17
|
-
const OPEN = { '{': '}', '(': ')', '[': ']' }
|
|
18
|
-
const CLOSE = { '}': '{', ')': '(', ']': '[' }
|
|
19
|
-
|
|
20
|
-
// Walk `src` once. `at(i)` returns the index just past a string or comment starting
|
|
21
|
-
// at i, or -1 if none starts there — so callers can skip over them uniformly.
|
|
22
|
-
function scannerAt(src, strings) {
|
|
23
|
-
const n = src.length
|
|
24
|
-
return function at(i) {
|
|
25
|
-
if (src[i] === '/' && src[i + 1] === '*') { let j = i + 2; while (j < n && !(src[j] === '*' && src[j + 1] === '/')) j++; return Math.min(n, j + 2) }
|
|
26
|
-
for (const lc of LINE) if (src.startsWith(lc, i)) { let j = i + lc.length; while (j < n && src[j] !== '\n') j++; return j }
|
|
27
|
-
if (strings.includes(src[i])) { const q = src[i]; let j = i + 1; while (j < n) { if (src[j] === '\\') { j += 2; continue } if (src[j] === q) { j++; break } j++ } return j }
|
|
28
|
-
return -1
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const isWS = (c) => c === ' ' || c === '\t' || c === '\r' || c === '\n'
|
|
33
|
-
|
|
34
|
-
// Extract top-level units [{start,end}] from src[from..to]. A unit is a maximal
|
|
35
|
-
// declaration/statement at bracket depth 0: either a block (signature + balanced
|
|
36
|
-
// {…}) or a statement ending at ';' or end-of-line.
|
|
37
|
-
function topUnits(src, strings, from = 0, to = src.length) {
|
|
38
|
-
const at = scannerAt(src, strings)
|
|
39
|
-
const units = []
|
|
40
|
-
let i = from
|
|
41
|
-
while (i < to) {
|
|
42
|
-
while (i < to && isWS(src[i])) i++
|
|
43
|
-
if (i >= to) break
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
if (
|
|
52
|
-
|
|
53
|
-
if (
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
//
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
//
|
|
99
|
-
//
|
|
100
|
-
function
|
|
101
|
-
const at = scannerAt(src, strings)
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
if (
|
|
107
|
-
const
|
|
108
|
-
if (
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
//
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
1
|
+
// Brace-family language provider for ICR — a dependency-free, language-agnostic
|
|
2
|
+
// structural splitter for C-style languages (TypeScript, Go, Rust, Java, C/C++,
|
|
3
|
+
// C#, Swift, Kotlin, Scala, PHP, Dart, …). It implements the ICR provider contract
|
|
4
|
+
// { id, exts, parses, units, declaredNames, splitUnit, parsesUnit } so the SAME
|
|
5
|
+
// merge engine in icr.js merges these languages by structure — different top-level
|
|
6
|
+
// declarations merge cleanly, the same declaration edited two ways is a named
|
|
7
|
+
// conflict, and edits to different members inside one class/function merge via the
|
|
8
|
+
// engine's finer-grained inner merge (splitUnit).
|
|
9
|
+
//
|
|
10
|
+
// It is a heuristic scanner, not a full parser: it tracks strings, line/block
|
|
11
|
+
// comments, and bracket depth to find top-level declarations and their byte ranges.
|
|
12
|
+
// This gives real structural merge and an approximate parses() (balanced delimiters)
|
|
13
|
+
// — honest and useful today; a tree-sitter provider can later replace it behind the
|
|
14
|
+
// same interface with zero engine changes.
|
|
15
|
+
|
|
16
|
+
const LINE = ['//']
|
|
17
|
+
const OPEN = { '{': '}', '(': ')', '[': ']' }
|
|
18
|
+
const CLOSE = { '}': '{', ')': '(', ']': '[' }
|
|
19
|
+
|
|
20
|
+
// Walk `src` once. `at(i)` returns the index just past a string or comment starting
|
|
21
|
+
// at i, or -1 if none starts there — so callers can skip over them uniformly.
|
|
22
|
+
function scannerAt(src, strings) {
|
|
23
|
+
const n = src.length
|
|
24
|
+
return function at(i) {
|
|
25
|
+
if (src[i] === '/' && src[i + 1] === '*') { let j = i + 2; while (j < n && !(src[j] === '*' && src[j + 1] === '/')) j++; return Math.min(n, j + 2) }
|
|
26
|
+
for (const lc of LINE) if (src.startsWith(lc, i)) { let j = i + lc.length; while (j < n && src[j] !== '\n') j++; return j }
|
|
27
|
+
if (strings.includes(src[i])) { const q = src[i]; let j = i + 1; while (j < n) { if (src[j] === '\\') { j += 2; continue } if (src[j] === q) { j++; break } j++ } return j }
|
|
28
|
+
return -1
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const isWS = (c) => c === ' ' || c === '\t' || c === '\r' || c === '\n'
|
|
33
|
+
|
|
34
|
+
// Extract top-level units [{start,end}] from src[from..to]. A unit is a maximal
|
|
35
|
+
// declaration/statement at bracket depth 0: either a block (signature + balanced
|
|
36
|
+
// {…}) or a statement ending at ';' or end-of-line.
|
|
37
|
+
function topUnits(src, strings, from = 0, to = src.length) {
|
|
38
|
+
const at = scannerAt(src, strings)
|
|
39
|
+
const units = []
|
|
40
|
+
let i = from
|
|
41
|
+
while (i < to) {
|
|
42
|
+
while (i < to && isWS(src[i])) i++
|
|
43
|
+
if (i >= to) break
|
|
44
|
+
|
|
45
|
+
const start = i
|
|
46
|
+
let depth = 0, sawBrace = false
|
|
47
|
+
while (i < to) {
|
|
48
|
+
const s = at(i)
|
|
49
|
+
if (s > i) { i = s; continue } // skip string / comment
|
|
50
|
+
const c = src[i]
|
|
51
|
+
if (OPEN[c]) { if (c === '{') sawBrace = true; depth++; i++; continue }
|
|
52
|
+
if (CLOSE[c]) { depth--; i++; if (depth <= 0 && sawBrace) { while (i < to && (src[i] === ' ' || src[i] === '\t')) i++; if (src[i] === ';') i++; break } continue }
|
|
53
|
+
if (depth === 0) {
|
|
54
|
+
if (c === ';') { i++; break }
|
|
55
|
+
if (c === '\n' && !sawBrace) break
|
|
56
|
+
}
|
|
57
|
+
i++
|
|
58
|
+
}
|
|
59
|
+
units.push({ start, end: i })
|
|
60
|
+
}
|
|
61
|
+
return units
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const DECL = /\b(function|func|fn|def|fun|class|interface|struct|enum|trait|impl|type|namespace|module|package|object|record|protocol|actor|extension)\s+([A-Za-z_$][\w$]*)/
|
|
65
|
+
const VARD = /\b(const|let|var|val|static|final)\s+([A-Za-z_$][\w$]*)/
|
|
66
|
+
// Method/function signature, including TYPE-FIRST forms (`int a()`, `public static
|
|
67
|
+
// void main(String[] a)`, `std::string name()`): optional decorators + any number of
|
|
68
|
+
// modifiers + an optional return-type token, then NAME(args){. The captured name is
|
|
69
|
+
// the identifier directly before `(`.
|
|
70
|
+
const METH = /^(?:@[\w.()]+\s+)*(?:(?:public|private|protected|internal|static|async|final|override|open|pub|suspend|inline|virtual|export|default|abstract|sealed|unsafe|extern|synchronized|native|constexpr)\s+)*(?:(?:function|fn|func|fun)\s+)?(?:[A-Za-z_$][\w$<>\[\].,:&*\s]*?\s+)?([A-Za-z_$][\w$]*)\s*(?:<[^>]*>)?\s*\([^;{]*\)\s*(?:->[^{]+|:[^={]+)?\s*\{/
|
|
71
|
+
// control-flow headers also look like `name (…) {` — never key them as methods
|
|
72
|
+
const CTRL = new Set(['if', 'for', 'while', 'switch', 'catch', 'do', 'else', 'try', 'return', 'new', 'throw', 'match', 'select', 'when', 'until', 'lock', 'using', 'foreach', 'with'])
|
|
73
|
+
|
|
74
|
+
function keyOf(text) {
|
|
75
|
+
const head = text.replace(/^\s+/, '')
|
|
76
|
+
let m = head.match(DECL); if (m) return m[1] + ':' + m[2]
|
|
77
|
+
m = head.match(METH); if (m && !CTRL.has(m[1])) return 'method:' + m[1]
|
|
78
|
+
m = head.match(VARD); if (m) return 'var:' + m[2]
|
|
79
|
+
return 'stmt:' + head.split('\n')[0].trim().replace(/\s+/g, ' ')
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// De-duplicate keys deterministically (repeated statements / same-named units) so the
|
|
83
|
+
// engine's keyed merge never silently drops one. Order is stable → convergent.
|
|
84
|
+
function keyedUnits(raw, src) {
|
|
85
|
+
const seen = new Map(), out = []
|
|
86
|
+
for (const u of raw) {
|
|
87
|
+
const text = src.slice(u.start, u.end)
|
|
88
|
+
let k = keyOf(text)
|
|
89
|
+
const c = (seen.get(k) || 0) + 1; seen.set(k, c)
|
|
90
|
+
if (c > 1) k += '#' + c
|
|
91
|
+
out.push({ key: k, text, start: u.start, end: u.end })
|
|
92
|
+
}
|
|
93
|
+
return out
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Generic lexer for the token-level inner merge + the intent layer. Tokens are
|
|
97
|
+
// identifiers, numbers, whole strings, and single operator chars; comments live in
|
|
98
|
+
// the gaps (same contract as the JS provider: a comment-only edit is a no-op here).
|
|
99
|
+
// Returns [{start,end,k}] — k is the raw text, so any content change is a token change.
|
|
100
|
+
function lexTokens(src, strings) {
|
|
101
|
+
const at = scannerAt(src, strings)
|
|
102
|
+
const out = []
|
|
103
|
+
const n = src.length
|
|
104
|
+
let i = 0
|
|
105
|
+
while (i < n) {
|
|
106
|
+
if (isWS(src[i])) { i++; continue }
|
|
107
|
+
const j = at(i)
|
|
108
|
+
if (j > i) {
|
|
109
|
+
if (src[i] === '/' || src[i] === '#') { i = j; continue } // comment → gap
|
|
110
|
+
out.push({ start: i, end: j, k: src.slice(i, j) }); i = j; continue // string → one token
|
|
111
|
+
}
|
|
112
|
+
const c = src[i]
|
|
113
|
+
if (/[A-Za-z_$]/.test(c)) { let e = i + 1; while (e < n && /[\w$]/.test(src[e])) e++; out.push({ start: i, end: e, k: src.slice(i, e) }); i = e; continue }
|
|
114
|
+
if (/[0-9]/.test(c)) { let e = i + 1; while (e < n && /[\w.]/.test(src[e])) e++; out.push({ start: i, end: e, k: src.slice(i, e) }); i = e; continue }
|
|
115
|
+
out.push({ start: i, end: i + 1, k: c }); i++
|
|
116
|
+
}
|
|
117
|
+
return out
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Is this token a REFERENCE-position identifier (not property access `.name`)?
|
|
121
|
+
function isRefToken(src, t) {
|
|
122
|
+
if (!/^[A-Za-z_$][\w$]*$/.test(t.k)) return false
|
|
123
|
+
let p = t.start - 1
|
|
124
|
+
while (p >= 0 && isWS(src[p])) p--
|
|
125
|
+
return !(p >= 0 && src[p] === '.')
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Approximate parse check: delimiters balanced and no unterminated string/comment,
|
|
129
|
+
// ignoring the contents of strings and comments.
|
|
130
|
+
function balanced(src, strings) {
|
|
131
|
+
const at = scannerAt(src, strings)
|
|
132
|
+
const stack = []
|
|
133
|
+
let i = 0
|
|
134
|
+
while (i < src.length) {
|
|
135
|
+
const s = at(i)
|
|
136
|
+
if (s > i) { if (s > src.length) return false; i = s; continue }
|
|
137
|
+
const c = src[i]
|
|
138
|
+
if (OPEN[c]) stack.push(c)
|
|
139
|
+
else if (CLOSE[c]) { if (stack.pop() !== CLOSE[c]) return false }
|
|
140
|
+
i++
|
|
141
|
+
}
|
|
142
|
+
return stack.length === 0
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function makeBraceLanguage({ id, exts, strings = ['"', "'", '`'] }) {
|
|
146
|
+
const provider = {
|
|
147
|
+
id,
|
|
148
|
+
exts,
|
|
149
|
+
parses: (src) => balanced(String(src == null ? '' : src), strings),
|
|
150
|
+
units: (src) => keyedUnits(topUnits(String(src), strings), String(src)),
|
|
151
|
+
declaredNames: (src) => {
|
|
152
|
+
const names = new Set()
|
|
153
|
+
for (const u of keyedUnits(topUnits(String(src), strings), String(src))) {
|
|
154
|
+
const m = u.key.match(/^(?:[a-z]+):([A-Za-z_$][\w$]*)/)
|
|
155
|
+
if (m && !u.key.startsWith('stmt:')) names.add(m[1])
|
|
156
|
+
}
|
|
157
|
+
return names
|
|
158
|
+
},
|
|
159
|
+
parsesUnit: (src) => balanced(String(src), strings),
|
|
160
|
+
// --- intent layer (heuristic but honest): dangling refs, rename, token merge ---
|
|
161
|
+
// Every identifier used in reference position (skips `.property` access). Approximate
|
|
162
|
+
// and OVER-inclusive — which only ever makes the dangling check MORE cautious: a
|
|
163
|
+
// deleted-but-still-referenced name is surfaced as a conflict, never shipped broken.
|
|
164
|
+
usedIdentifiers: (src) => {
|
|
165
|
+
src = String(src)
|
|
166
|
+
const used = new Set()
|
|
167
|
+
for (const t of lexTokens(src, strings)) if (isRefToken(src, t)) used.add(t.k)
|
|
168
|
+
return used
|
|
169
|
+
},
|
|
170
|
+
// The declaration's text with its own name excluded, so two declarations that differ
|
|
171
|
+
// only in their name compare equal — that's how the engine spots a rename.
|
|
172
|
+
declBody: (src, name) => {
|
|
173
|
+
src = String(src)
|
|
174
|
+
for (const u of keyedUnits(topUnits(src, strings), src)) {
|
|
175
|
+
const m = u.key.match(/^([a-z]+):(.+?)(?:#\d+)?$/)
|
|
176
|
+
if (!m || m[1] === 'stmt' || m[2] !== name) continue
|
|
177
|
+
return u.text.replace(name, '').trimEnd() // trailing whitespace must not defeat the rename match
|
|
178
|
+
}
|
|
179
|
+
return null
|
|
180
|
+
},
|
|
181
|
+
// Rewrite reference-position uses of oldName (never `.oldName` property access).
|
|
182
|
+
// Back-to-front so offsets stay valid. The engine only accepts the rewrite if the
|
|
183
|
+
// result still passes parses(), so a bad rewrite can't ship.
|
|
184
|
+
renameRefs: (src, oldName, newName) => {
|
|
185
|
+
src = String(src)
|
|
186
|
+
const spots = []
|
|
187
|
+
for (const t of lexTokens(src, strings)) if (t.k === oldName && isRefToken(src, t)) spots.push(t)
|
|
188
|
+
let out = src
|
|
189
|
+
for (const t of spots.reverse()) out = out.slice(0, t.start) + newName + out.slice(t.end)
|
|
190
|
+
return out
|
|
191
|
+
},
|
|
192
|
+
// Finest granularity: token stream for the engine's token-level inner merge.
|
|
193
|
+
tokenize: (src) => lexTokens(String(src), strings),
|
|
194
|
+
// Finer granularity: descend one level into a block declaration so two agents
|
|
195
|
+
// editing DIFFERENT members of the same class/function merge instead of clashing.
|
|
196
|
+
splitUnit: (text) => {
|
|
197
|
+
const src = String(text)
|
|
198
|
+
// Only descend into NAMED containers (class/struct/impl/…), whose members are
|
|
199
|
+
// keyed by name — so editing different members merges. A function/method body is
|
|
200
|
+
// just statements (no stable keys); descending there would falsely merge two
|
|
201
|
+
// edits to the same code, so we DON'T — the whole unit becomes a clean conflict.
|
|
202
|
+
if (!/\b(class|interface|struct|enum|trait|impl|namespace|module|object|record|extension|protocol|actor)\b/.test(src.split('{')[0]))
|
|
203
|
+
return null
|
|
204
|
+
const at = scannerAt(src, strings)
|
|
205
|
+
let i = 0, open = -1
|
|
206
|
+
while (i < src.length) { const s = at(i); if (s > i) { i = s; continue } if (src[i] === '{') { open = i; break } i++ }
|
|
207
|
+
if (open < 0) return null
|
|
208
|
+
// find matching close
|
|
209
|
+
let depth = 0, j = open, close = -1
|
|
210
|
+
while (j < src.length) { const s = at(j); if (s > j) { j = s; continue } const c = src[j]; if (c === '{') depth++; else if (c === '}') { depth--; if (depth === 0) { close = j; break } } j++ }
|
|
211
|
+
if (close < 0) return null
|
|
212
|
+
const sig = src.slice(0, open)
|
|
213
|
+
const body = src.slice(open + 1, close)
|
|
214
|
+
const inner = keyedUnits(topUnits(body, strings), body)
|
|
215
|
+
// inner ranges are already in `body` coordinates (topUnits scanned body). spliceable:
|
|
216
|
+
// the engine rebuilds the body by splicing merged members back into it, so the class's
|
|
217
|
+
// original indentation and any comments between members survive (no brace-glue reflow).
|
|
218
|
+
return { sig, open: '{', close: src.slice(close), body, units: inner, join: '\n', spliceable: true }
|
|
219
|
+
},
|
|
220
|
+
}
|
|
221
|
+
return provider
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Ready-made providers covering the common C-family languages. One provider can claim
|
|
225
|
+
// many extensions; comment/string syntax defaults fit the whole family (edge cases like
|
|
226
|
+
// Rust raw strings / lifetimes are tolerated — worst case a unit is merged coarsely).
|
|
227
|
+
export const braceLanguages = [
|
|
228
|
+
makeBraceLanguage({ id: 'ts', exts: ['.ts', '.tsx', '.mts', '.cts'] }),
|
|
229
|
+
makeBraceLanguage({ id: 'jsx', exts: ['.jsx'] }),
|
|
230
|
+
makeBraceLanguage({ id: 'go', exts: ['.go'], strings: ['"', '`'] }),
|
|
231
|
+
makeBraceLanguage({ id: 'rust', exts: ['.rs'] }),
|
|
232
|
+
makeBraceLanguage({ id: 'java', exts: ['.java'] }),
|
|
233
|
+
makeBraceLanguage({ id: 'c', exts: ['.c', '.h', '.cpp', '.hpp', '.cc', '.hh', '.cxx'] }),
|
|
234
|
+
makeBraceLanguage({ id: 'csharp', exts: ['.cs'] }),
|
|
235
|
+
makeBraceLanguage({ id: 'swift', exts: ['.swift'] }),
|
|
236
|
+
makeBraceLanguage({ id: 'kotlin', exts: ['.kt', '.kts'] }),
|
|
237
|
+
makeBraceLanguage({ id: 'scala', exts: ['.scala'] }),
|
|
238
|
+
makeBraceLanguage({ id: 'php', exts: ['.php'] }),
|
|
239
|
+
makeBraceLanguage({ id: 'dart', exts: ['.dart'] }),
|
|
240
|
+
]
|