tjs-lang 0.2.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.
Files changed (91) hide show
  1. package/CONTEXT.md +594 -0
  2. package/LICENSE +190 -0
  3. package/README.md +220 -0
  4. package/bin/benchmarks.ts +351 -0
  5. package/bin/dev.ts +205 -0
  6. package/bin/docs.js +170 -0
  7. package/bin/install-cursor.sh +71 -0
  8. package/bin/install-vscode.sh +71 -0
  9. package/bin/select-local-models.d.ts +1 -0
  10. package/bin/select-local-models.js +28 -0
  11. package/bin/select-local-models.ts +31 -0
  12. package/demo/autocomplete.test.ts +232 -0
  13. package/demo/docs.json +186 -0
  14. package/demo/examples.test.ts +598 -0
  15. package/demo/index.html +91 -0
  16. package/demo/src/autocomplete.ts +482 -0
  17. package/demo/src/capabilities.ts +859 -0
  18. package/demo/src/demo-nav.ts +2097 -0
  19. package/demo/src/examples.test.ts +161 -0
  20. package/demo/src/examples.ts +476 -0
  21. package/demo/src/imports.test.ts +196 -0
  22. package/demo/src/imports.ts +421 -0
  23. package/demo/src/index.ts +639 -0
  24. package/demo/src/module-store.ts +635 -0
  25. package/demo/src/module-sw.ts +132 -0
  26. package/demo/src/playground.ts +949 -0
  27. package/demo/src/service-host.ts +389 -0
  28. package/demo/src/settings.ts +440 -0
  29. package/demo/src/style.ts +280 -0
  30. package/demo/src/tjs-playground.ts +1605 -0
  31. package/demo/src/ts-examples.ts +478 -0
  32. package/demo/src/ts-playground.ts +1092 -0
  33. package/demo/static/favicon.svg +30 -0
  34. package/demo/static/photo-1.jpg +0 -0
  35. package/demo/static/photo-2.jpg +0 -0
  36. package/demo/static/texts/ai-history.txt +9 -0
  37. package/demo/static/texts/coffee-origins.txt +9 -0
  38. package/demo/static/texts/renewable-energy.txt +9 -0
  39. package/dist/index.js +256 -0
  40. package/dist/index.js.map +37 -0
  41. package/dist/tjs-batteries.js +4 -0
  42. package/dist/tjs-batteries.js.map +15 -0
  43. package/dist/tjs-full.js +256 -0
  44. package/dist/tjs-full.js.map +37 -0
  45. package/dist/tjs-transpiler.js +220 -0
  46. package/dist/tjs-transpiler.js.map +21 -0
  47. package/dist/tjs-vm.js +4 -0
  48. package/dist/tjs-vm.js.map +14 -0
  49. package/docs/CNAME +1 -0
  50. package/docs/favicon.svg +30 -0
  51. package/docs/index.html +91 -0
  52. package/docs/index.js +10468 -0
  53. package/docs/index.js.map +92 -0
  54. package/docs/photo-1.jpg +0 -0
  55. package/docs/photo-1.webp +0 -0
  56. package/docs/photo-2.jpg +0 -0
  57. package/docs/photo-2.webp +0 -0
  58. package/docs/texts/ai-history.txt +9 -0
  59. package/docs/texts/coffee-origins.txt +9 -0
  60. package/docs/texts/renewable-energy.txt +9 -0
  61. package/docs/tjs-lang.svg +31 -0
  62. package/docs/tosijs-agent.svg +31 -0
  63. package/editors/README.md +325 -0
  64. package/editors/ace/ajs-mode.js +328 -0
  65. package/editors/ace/ajs-mode.ts +269 -0
  66. package/editors/ajs-syntax.ts +212 -0
  67. package/editors/build-grammars.ts +510 -0
  68. package/editors/codemirror/ajs-language.js +287 -0
  69. package/editors/codemirror/ajs-language.ts +1447 -0
  70. package/editors/codemirror/autocomplete.test.ts +531 -0
  71. package/editors/codemirror/component.ts +404 -0
  72. package/editors/monaco/ajs-monarch.js +243 -0
  73. package/editors/monaco/ajs-monarch.ts +225 -0
  74. package/editors/tjs-syntax.ts +115 -0
  75. package/editors/vscode/language-configuration.json +37 -0
  76. package/editors/vscode/package.json +65 -0
  77. package/editors/vscode/syntaxes/ajs-injection.tmLanguage.json +107 -0
  78. package/editors/vscode/syntaxes/ajs.tmLanguage.json +252 -0
  79. package/editors/vscode/syntaxes/tjs.tmLanguage.json +333 -0
  80. package/package.json +83 -0
  81. package/src/cli/commands/check.ts +41 -0
  82. package/src/cli/commands/convert.ts +133 -0
  83. package/src/cli/commands/emit.ts +260 -0
  84. package/src/cli/commands/run.ts +68 -0
  85. package/src/cli/commands/test.ts +194 -0
  86. package/src/cli/commands/types.ts +20 -0
  87. package/src/cli/create-app.ts +236 -0
  88. package/src/cli/playground.ts +250 -0
  89. package/src/cli/tjs.ts +166 -0
  90. package/src/cli/tjsx.ts +160 -0
  91. package/tjs-lang.svg +31 -0
@@ -0,0 +1,482 @@
1
+ /**
2
+ * TJS Autocompletion Provider
3
+ *
4
+ * Provides context-aware completions for the TJS playground.
5
+ * Uses __tjs metadata for rich type information.
6
+ */
7
+
8
+ export interface Completion {
9
+ /** The text to insert */
10
+ label: string
11
+ /** What kind of completion (function, variable, property, keyword) */
12
+ kind: 'function' | 'variable' | 'property' | 'keyword' | 'type'
13
+ /** Description shown in autocomplete popup */
14
+ detail?: string
15
+ /** Full documentation (markdown supported) */
16
+ documentation?: string
17
+ /** Text to insert (if different from label) */
18
+ insertText?: string
19
+ /** Snippet with tab stops: ${1:param} */
20
+ snippet?: string
21
+ }
22
+
23
+ export interface CompletionContext {
24
+ /** Source code being edited */
25
+ source: string
26
+ /** Cursor position (character offset) */
27
+ position: number
28
+ /** Parsed __tjs metadata from current file */
29
+ metadata?: Record<string, TJSMetadata>
30
+ /** Imported module metadata */
31
+ imports?: Record<string, Record<string, TJSMetadata>>
32
+ }
33
+
34
+ export interface TJSMetadata {
35
+ params?: Record<string, ParamInfo>
36
+ returns?: { type: string }
37
+ description?: string
38
+ typeParams?: Record<string, { constraint?: string; default?: string }>
39
+ }
40
+
41
+ interface ParamInfo {
42
+ type: string
43
+ required: boolean
44
+ default?: any
45
+ description?: string
46
+ }
47
+
48
+ // TJS keywords
49
+ const TJS_KEYWORDS: Completion[] = [
50
+ { label: 'function', kind: 'keyword', detail: 'Declare a function' },
51
+ { label: 'const', kind: 'keyword', detail: 'Declare a constant' },
52
+ { label: 'let', kind: 'keyword', detail: 'Declare a variable' },
53
+ { label: 'if', kind: 'keyword', detail: 'Conditional statement' },
54
+ { label: 'else', kind: 'keyword', detail: 'Else branch' },
55
+ { label: 'while', kind: 'keyword', detail: 'While loop' },
56
+ { label: 'for', kind: 'keyword', detail: 'For loop' },
57
+ { label: 'return', kind: 'keyword', detail: 'Return from function' },
58
+ { label: 'try', kind: 'keyword', detail: 'Try block' },
59
+ { label: 'catch', kind: 'keyword', detail: 'Catch block' },
60
+ { label: 'async', kind: 'keyword', detail: 'Async function' },
61
+ { label: 'await', kind: 'keyword', detail: 'Await promise' },
62
+ { label: 'import', kind: 'keyword', detail: 'Import module' },
63
+ { label: 'export', kind: 'keyword', detail: 'Export declaration' },
64
+ {
65
+ label: 'test',
66
+ kind: 'keyword',
67
+ detail: 'Inline test block',
68
+ snippet: "test('${1:description}') {\n ${2:// test code}\n}",
69
+ },
70
+ {
71
+ label: 'mock',
72
+ kind: 'keyword',
73
+ detail: 'Mock setup block',
74
+ snippet: 'mock {\n ${1:// setup code}\n}',
75
+ },
76
+ {
77
+ label: 'unsafe',
78
+ kind: 'keyword',
79
+ detail: 'Skip type validation',
80
+ snippet: 'unsafe {\n ${1:// unvalidated code}\n}',
81
+ },
82
+ ]
83
+
84
+ // TJS type examples
85
+ const TJS_TYPES: Completion[] = [
86
+ { label: "''", kind: 'type', detail: 'String type', insertText: "''" },
87
+ { label: '0', kind: 'type', detail: 'Number type', insertText: '0' },
88
+ { label: 'true', kind: 'type', detail: 'Boolean type', insertText: 'true' },
89
+ { label: 'null', kind: 'type', detail: 'Null type', insertText: 'null' },
90
+ {
91
+ label: 'undefined',
92
+ kind: 'type',
93
+ detail: 'Undefined type',
94
+ insertText: 'undefined',
95
+ },
96
+ {
97
+ label: "['']",
98
+ kind: 'type',
99
+ detail: 'Array of strings',
100
+ insertText: "['']",
101
+ },
102
+ { label: '[0]', kind: 'type', detail: 'Array of numbers', insertText: '[0]' },
103
+ { label: '{}', kind: 'type', detail: 'Object type', insertText: '{}' },
104
+ { label: 'any', kind: 'type', detail: 'Any type', insertText: 'any' },
105
+ ]
106
+
107
+ // Runtime globals
108
+ const RUNTIME_COMPLETIONS: Completion[] = [
109
+ {
110
+ label: 'isError',
111
+ kind: 'function',
112
+ detail: '(value: any) -> boolean',
113
+ documentation: 'Check if a value is a TJS error',
114
+ snippet: 'isError(${1:value})',
115
+ },
116
+ {
117
+ label: 'error',
118
+ kind: 'function',
119
+ detail: '(message: string, details?: object) -> TJSError',
120
+ documentation: 'Create a TJS error object',
121
+ snippet: "error('${1:message}')",
122
+ },
123
+ {
124
+ label: 'typeOf',
125
+ kind: 'function',
126
+ detail: '(value: any) -> string',
127
+ documentation: 'Get type name (fixed typeof - handles null, arrays)',
128
+ snippet: 'typeOf(${1:value})',
129
+ },
130
+ {
131
+ label: 'wrap',
132
+ kind: 'function',
133
+ detail: '(fn: Function, meta: TJSMeta) -> Function',
134
+ documentation: 'Wrap function with runtime type validation',
135
+ snippet: 'wrap(${1:fn}, ${2:meta})',
136
+ },
137
+ {
138
+ label: 'expect',
139
+ kind: 'function',
140
+ detail: '(actual: any) -> Matchers',
141
+ documentation: 'Test assertion (in test blocks)',
142
+ snippet: 'expect(${1:actual})',
143
+ },
144
+ ]
145
+
146
+ // Common assertion matchers
147
+ const EXPECT_MATCHERS: Completion[] = [
148
+ {
149
+ label: 'toBe',
150
+ kind: 'function',
151
+ detail: '(expected: any) -> void',
152
+ documentation: 'Strict equality check (===)',
153
+ snippet: 'toBe(${1:expected})',
154
+ },
155
+ {
156
+ label: 'toEqual',
157
+ kind: 'function',
158
+ detail: '(expected: any) -> void',
159
+ documentation: 'Deep equality check',
160
+ snippet: 'toEqual(${1:expected})',
161
+ },
162
+ {
163
+ label: 'toContain',
164
+ kind: 'function',
165
+ detail: '(item: any) -> void',
166
+ documentation: 'Array/string contains check',
167
+ snippet: 'toContain(${1:item})',
168
+ },
169
+ {
170
+ label: 'toThrow',
171
+ kind: 'function',
172
+ detail: '(message?: string) -> void',
173
+ documentation: 'Check that function throws',
174
+ snippet: 'toThrow(${1:message})',
175
+ },
176
+ {
177
+ label: 'toBeTruthy',
178
+ kind: 'property',
179
+ detail: 'getter',
180
+ documentation: 'Check value is truthy',
181
+ },
182
+ {
183
+ label: 'toBeFalsy',
184
+ kind: 'property',
185
+ detail: 'getter',
186
+ documentation: 'Check value is falsy',
187
+ },
188
+ {
189
+ label: 'toBeNull',
190
+ kind: 'property',
191
+ detail: 'getter',
192
+ documentation: 'Check value is null',
193
+ },
194
+ {
195
+ label: 'toBeUndefined',
196
+ kind: 'property',
197
+ detail: 'getter',
198
+ documentation: 'Check value is undefined',
199
+ },
200
+ {
201
+ label: 'toBeGreaterThan',
202
+ kind: 'function',
203
+ detail: '(expected: number) -> void',
204
+ documentation: 'Check value is greater than expected',
205
+ snippet: 'toBeGreaterThan(${1:expected})',
206
+ },
207
+ {
208
+ label: 'toBeLessThan',
209
+ kind: 'function',
210
+ detail: '(expected: number) -> void',
211
+ documentation: 'Check value is less than expected',
212
+ snippet: 'toBeLessThan(${1:expected})',
213
+ },
214
+ {
215
+ label: 'toBeGreaterThanOrEqual',
216
+ kind: 'function',
217
+ detail: '(expected: number) -> void',
218
+ documentation: 'Check value is >= expected',
219
+ snippet: 'toBeGreaterThanOrEqual(${1:expected})',
220
+ },
221
+ {
222
+ label: 'toBeLessThanOrEqual',
223
+ kind: 'function',
224
+ detail: '(expected: number) -> void',
225
+ documentation: 'Check value is <= expected',
226
+ snippet: 'toBeLessThanOrEqual(${1:expected})',
227
+ },
228
+ ]
229
+
230
+ /**
231
+ * Get the word being typed at the cursor position
232
+ */
233
+ function getWordAtPosition(
234
+ source: string,
235
+ position: number
236
+ ): { word: string; start: number } {
237
+ let start = position
238
+ while (start > 0 && /[\w$]/.test(source[start - 1])) {
239
+ start--
240
+ }
241
+ return {
242
+ word: source.slice(start, position),
243
+ start,
244
+ }
245
+ }
246
+
247
+ /**
248
+ * Get context around cursor (what's before the word)
249
+ */
250
+ function getContextBefore(source: string, start: number): string {
251
+ // Look back for context (dot, opening paren, etc.)
252
+ let contextStart = start - 1
253
+ while (contextStart >= 0 && /\s/.test(source[contextStart])) {
254
+ contextStart--
255
+ }
256
+
257
+ // Get the previous 50 chars for context
258
+ const contextEnd = contextStart + 1
259
+ const contextBegin = Math.max(0, contextEnd - 50)
260
+ return source.slice(contextBegin, contextEnd)
261
+ }
262
+
263
+ /**
264
+ * Check if we're inside a test block
265
+ */
266
+ function isInTestBlock(source: string, position: number): boolean {
267
+ // Simple heuristic: look for unmatched `test(` before position
268
+ const before = source.slice(0, position)
269
+ const testMatches = before.match(/test\s*\([^)]*\)\s*\{/g) || []
270
+ const closeBraces = (before.match(/\}/g) || []).length
271
+
272
+ // Very rough: if we have more test opens than closes, we're in a test
273
+ return testMatches.length > 0
274
+ }
275
+
276
+ /**
277
+ * Extract function names from source
278
+ */
279
+ function extractFunctions(source: string): Completion[] {
280
+ const completions: Completion[] = []
281
+ const funcRegex = /function\s+(\w+)\s*\(([^)]*)\)/g
282
+
283
+ let match
284
+ while ((match = funcRegex.exec(source)) !== null) {
285
+ const [, name, params] = match
286
+ completions.push({
287
+ label: name,
288
+ kind: 'function',
289
+ detail: `(${params})`,
290
+ snippet: `${name}(${params ? '${1}' : ''})`,
291
+ })
292
+ }
293
+
294
+ return completions
295
+ }
296
+
297
+ /**
298
+ * Extract variable names from source
299
+ */
300
+ function extractVariables(source: string, position: number): Completion[] {
301
+ const completions: Completion[] = []
302
+ const before = source.slice(0, position)
303
+
304
+ // Match const/let declarations
305
+ const varRegex = /(?:const|let)\s+(\w+)\s*=/g
306
+ let match
307
+ while ((match = varRegex.exec(before)) !== null) {
308
+ completions.push({
309
+ label: match[1],
310
+ kind: 'variable',
311
+ })
312
+ }
313
+
314
+ return completions
315
+ }
316
+
317
+ /**
318
+ * Build completions from __tjs metadata
319
+ */
320
+ function completionsFromMetadata(
321
+ metadata: Record<string, TJSMetadata>
322
+ ): Completion[] {
323
+ const completions: Completion[] = []
324
+
325
+ for (const [name, meta] of Object.entries(metadata)) {
326
+ const params = meta.params ? Object.keys(meta.params).join(', ') : ''
327
+ const returnType = meta.returns?.type || 'void'
328
+
329
+ completions.push({
330
+ label: name,
331
+ kind: 'function',
332
+ detail: `(${params}) -> ${returnType}`,
333
+ documentation: meta.description,
334
+ snippet: params
335
+ ? `${name}(${Object.keys(meta.params || {})
336
+ .map((p, i) => `\${${i + 1}:${p}}`)
337
+ .join(', ')})`
338
+ : `${name}()`,
339
+ })
340
+ }
341
+
342
+ return completions
343
+ }
344
+
345
+ /**
346
+ * Get autocompletions for the current context
347
+ */
348
+ export function getCompletions(ctx: CompletionContext): Completion[] {
349
+ const { source, position, metadata, imports } = ctx
350
+ const { word, start } = getWordAtPosition(source, position)
351
+ const contextBefore = getContextBefore(source, start)
352
+
353
+ let completions: Completion[] = []
354
+
355
+ // After a dot - property/method access
356
+ if (contextBefore.endsWith('.')) {
357
+ // Check if it's expect().
358
+ if (/expect\s*\([^)]*\)\s*\.$/.test(contextBefore)) {
359
+ completions = [...EXPECT_MATCHERS]
360
+ }
361
+ // Could add more dot-completion contexts here
362
+ }
363
+ // After : in function params - type context
364
+ else if (/:\s*$/.test(contextBefore)) {
365
+ completions = [...TJS_TYPES]
366
+ }
367
+ // After -> return type
368
+ else if (/->\s*$/.test(contextBefore)) {
369
+ completions = [...TJS_TYPES]
370
+ }
371
+ // General completions
372
+ else {
373
+ completions = [
374
+ ...TJS_KEYWORDS,
375
+ ...RUNTIME_COMPLETIONS,
376
+ ...extractFunctions(source),
377
+ ...extractVariables(source, position),
378
+ ]
379
+
380
+ // Add metadata-based completions
381
+ if (metadata) {
382
+ completions.push(...completionsFromMetadata(metadata))
383
+ }
384
+
385
+ // Add import-based completions
386
+ if (imports) {
387
+ for (const [moduleName, moduleMeta] of Object.entries(imports)) {
388
+ completions.push(...completionsFromMetadata(moduleMeta))
389
+ }
390
+ }
391
+
392
+ // Add test-specific completions if in test block
393
+ if (isInTestBlock(source, position)) {
394
+ completions.push({
395
+ label: 'expect',
396
+ kind: 'function',
397
+ detail: '(actual: any) -> Matchers',
398
+ documentation: 'Create test assertion',
399
+ snippet: 'expect(${1:actual}).toBe(${2:expected})',
400
+ })
401
+ }
402
+ }
403
+
404
+ // Filter by prefix
405
+ if (word) {
406
+ const lowerWord = word.toLowerCase()
407
+ completions = completions.filter((c) =>
408
+ c.label.toLowerCase().startsWith(lowerWord)
409
+ )
410
+ }
411
+
412
+ // Sort: exact match first, then by kind, then alphabetically
413
+ completions.sort((a, b) => {
414
+ // Exact match first
415
+ if (a.label === word) return -1
416
+ if (b.label === word) return 1
417
+
418
+ // Then by kind priority
419
+ const kindPriority: Record<string, number> = {
420
+ function: 0,
421
+ variable: 1,
422
+ property: 2,
423
+ keyword: 3,
424
+ type: 4,
425
+ }
426
+ const aPriority = kindPriority[a.kind] ?? 5
427
+ const bPriority = kindPriority[b.kind] ?? 5
428
+ if (aPriority !== bPriority) return aPriority - bPriority
429
+
430
+ // Then alphabetically
431
+ return a.label.localeCompare(b.label)
432
+ })
433
+
434
+ return completions
435
+ }
436
+
437
+ /**
438
+ * Get signature help for function calls
439
+ */
440
+ export function getSignatureHelp(
441
+ ctx: CompletionContext
442
+ ): { signature: string; activeParam: number; params: string[] } | null {
443
+ const { source, position, metadata } = ctx
444
+
445
+ // Find the function call we're in
446
+ const before = source.slice(0, position)
447
+ const callMatch = before.match(/(\w+)\s*\(([^)]*)$/)
448
+
449
+ if (!callMatch) return null
450
+
451
+ const [, funcName, argsSoFar] = callMatch
452
+
453
+ // Count commas to determine active parameter
454
+ const activeParam = (argsSoFar.match(/,/g) || []).length
455
+
456
+ // Look up function metadata
457
+ const meta = metadata?.[funcName]
458
+ if (meta?.params) {
459
+ const params = Object.entries(meta.params).map(([name, info]) => {
460
+ const req = info.required ? '' : '?'
461
+ return `${name}${req}: ${info.type}`
462
+ })
463
+
464
+ return {
465
+ signature: `${funcName}(${params.join(', ')})`,
466
+ activeParam,
467
+ params,
468
+ }
469
+ }
470
+
471
+ // Check runtime functions
472
+ const runtimeFunc = RUNTIME_COMPLETIONS.find((c) => c.label === funcName)
473
+ if (runtimeFunc?.detail) {
474
+ return {
475
+ signature: `${funcName}${runtimeFunc.detail}`,
476
+ activeParam,
477
+ params: [],
478
+ }
479
+ }
480
+
481
+ return null
482
+ }