@ktjs/ts-plugin 0.1.3 → 0.1.4

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 (3) hide show
  1. package/README.md +4 -2
  2. package/dist/index.js +543 -21
  3. package/package.json +7 -7
package/README.md CHANGED
@@ -6,12 +6,12 @@ TypeScript language service plugin for KT.js `k-for` scope variables in TSX.
6
6
 
7
7
  - Editor-only enhancement (tsserver), no runtime transform.
8
8
  - Suppresses TS2304 (`Cannot find name ...`) for aliases declared by `k-for`.
9
+ - Infers alias types from iterable/indexed sources (for example `k-for="item in users"` makes `item` resolve to `users[number]`).
10
+ - Provides hover type info and member completions for inferred aliases.
9
11
  - Supports Vue-like syntax:
10
12
  - `k-for="item in list"`
11
13
  - `k-for="(item, i) in list"`
12
14
  - `k-for="(value, key, i) in mapLike"`
13
- - Keeps legacy fallback mode:
14
- - `k-for={list}` with `k-for-item` / `k-for-index` (or configured defaults).
15
15
 
16
16
  ## Install
17
17
 
@@ -19,6 +19,8 @@ TypeScript language service plugin for KT.js `k-for` scope variables in TSX.
19
19
  pnpm add -D @ktjs/ts-plugin typescript
20
20
  ```
21
21
 
22
+ Then open a `.tsx` or `.ts` file in your editor, press `Ctrl+Shift+P` and select "TypeScript: Select TypeScript Version", then choose "Use workspace version" to make sure your editor uses the version of TypeScript where the plugin is installed.
23
+
22
24
  ## Where to install it
23
25
 
24
26
  Install it in the same project/workspace that owns the `tsconfig.json` where you enable the plugin.
package/dist/index.js CHANGED
@@ -7,6 +7,7 @@ const DIAGNOSTIC_CANNOT_FIND_NAME = 2304;
7
7
  const IDENTIFIER_RE = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
8
8
  const KFOR_SINGLE_PATTERN = /^([A-Za-z_$][A-Za-z0-9_$]*)\s+(in|of)\s+([\s\S]+)$/;
9
9
  const KFOR_TUPLE_PATTERN = /^\(\s*([A-Za-z_$][A-Za-z0-9_$]*)(?:\s*,\s*([A-Za-z_$][A-Za-z0-9_$]*))?(?:\s*,\s*([A-Za-z_$][A-Za-z0-9_$]*))?\s*\)\s+(in|of)\s+([\s\S]+)$/;
10
+ const MEMBER_COMPLETION_PATTERN = /([A-Za-z_$][A-Za-z0-9_$]*(?:\s*(?:\.\s*[A-Za-z_$][A-Za-z0-9_$]*|\[\s*(?:'[^'\\]*(?:\\.[^'\\]*)*'|"[^"\\]*(?:\\.[^"\\]*)*"|\d+)\s*\]))*)\s*\.\s*([A-Za-z_$][A-Za-z0-9_$]*)?$/;
10
11
  function init(modules) {
11
12
  const ts = modules.typescript;
12
13
  function create(info) {
@@ -22,25 +23,84 @@ function init(modules) {
22
23
  if (!isJsxLikeFile(fileName)) {
23
24
  return diagnostics;
24
25
  }
25
- const sourceFile = languageService.getProgram()?.getSourceFile(fileName);
26
- if (!sourceFile) {
27
- return diagnostics;
28
- }
29
- const scopes = collectKForScopes(sourceFile, ts, config);
30
- if (scopes.length === 0) {
26
+ const analysis = getFileAnalysis(fileName, languageService, ts, config);
27
+ if (!analysis) {
31
28
  return diagnostics;
32
29
  }
33
30
  return diagnostics.filter((diag) => {
34
31
  if (diag.code !== DIAGNOSTIC_CANNOT_FIND_NAME || diag.start == null || diag.length == null) {
35
32
  return true;
36
33
  }
37
- const name = sourceFile.text.slice(diag.start, diag.start + diag.length).trim();
34
+ const name = analysis.sourceFile.text.slice(diag.start, diag.start + diag.length).trim();
38
35
  if (!isValidIdentifier(name)) {
39
36
  return true;
40
37
  }
41
- return !isSuppressed(diag.start, name, scopes);
38
+ return !isSuppressed(diag.start, name, analysis.scopes);
42
39
  });
43
40
  };
41
+ proxy.getQuickInfoAtPosition = (fileName, position) => {
42
+ const quickInfo = languageService.getQuickInfoAtPosition(fileName, position);
43
+ if (!isJsxLikeFile(fileName)) {
44
+ return quickInfo;
45
+ }
46
+ const analysis = getFileAnalysis(fileName, languageService, ts, config);
47
+ if (!analysis) {
48
+ return quickInfo;
49
+ }
50
+ const identifier = findIdentifierAtPosition(analysis.sourceFile, position, ts);
51
+ if (!identifier) {
52
+ return quickInfo;
53
+ }
54
+ const bindings = collectBindingsAtPosition(position, analysis.scopes);
55
+ const binding = bindings.get(identifier.text);
56
+ if (!binding) {
57
+ return quickInfo;
58
+ }
59
+ const typeText = formatTypeList(binding.types, analysis.checker, identifier, ts);
60
+ return {
61
+ kind: ts.ScriptElementKind.localVariableElement,
62
+ kindModifiers: '',
63
+ textSpan: {
64
+ start: identifier.getStart(analysis.sourceFile),
65
+ length: identifier.getWidth(analysis.sourceFile),
66
+ },
67
+ displayParts: [{ text: `(k-for) ${binding.name}: ${typeText}`, kind: 'text' }],
68
+ };
69
+ };
70
+ proxy.getCompletionsAtPosition = (fileName, position, options, formattingSettings) => {
71
+ const completions = languageService.getCompletionsAtPosition(fileName, position, options, formattingSettings);
72
+ if (!isJsxLikeFile(fileName)) {
73
+ return completions;
74
+ }
75
+ const analysis = getFileAnalysis(fileName, languageService, ts, config);
76
+ if (!analysis) {
77
+ return completions;
78
+ }
79
+ const bindings = collectBindingsAtPosition(position, analysis.scopes);
80
+ if (bindings.size === 0) {
81
+ return completions;
82
+ }
83
+ const contextNode = findInnermostNode(analysis.sourceFile, normalizePosition(position, analysis.sourceFile), ts) || analysis.sourceFile;
84
+ const localBindings = createBindingTypeMap(bindings);
85
+ const memberContext = getMemberCompletionContext(analysis.sourceFile.text, position);
86
+ if (memberContext) {
87
+ const receiverTypes = resolveExpressionTypesFromText(memberContext.receiver, {
88
+ checker: analysis.checker,
89
+ ts,
90
+ scopeNode: contextNode,
91
+ localBindings,
92
+ });
93
+ const memberEntries = createMemberCompletionEntries(receiverTypes, memberContext.prefix, analysis.checker, contextNode, ts);
94
+ if (memberEntries.length > 0) {
95
+ return mergeCompletionInfo(completions, memberEntries, true);
96
+ }
97
+ }
98
+ const aliasEntries = createAliasCompletionEntries(bindings, analysis.sourceFile.text, position, ts);
99
+ if (aliasEntries.length > 0) {
100
+ return mergeCompletionInfo(completions, aliasEntries, false);
101
+ }
102
+ return completions;
103
+ };
44
104
  return proxy;
45
105
  }
46
106
  return { create };
@@ -59,17 +119,33 @@ function isJsxLikeFile(fileName) {
59
119
  const ext = node_path_1.default.extname(fileName).toLowerCase();
60
120
  return ext === '.tsx' || ext === '.jsx';
61
121
  }
62
- function collectKForScopes(sourceFile, ts, config) {
122
+ function getFileAnalysis(fileName, languageService, ts, config) {
123
+ const program = languageService.getProgram();
124
+ if (!program) {
125
+ return undefined;
126
+ }
127
+ const sourceFile = program.getSourceFile(fileName);
128
+ if (!sourceFile) {
129
+ return undefined;
130
+ }
131
+ const checker = program.getTypeChecker();
132
+ const scopes = collectKForScopes(sourceFile, checker, ts, config);
133
+ if (scopes.length === 0) {
134
+ return undefined;
135
+ }
136
+ return { sourceFile, checker, scopes };
137
+ }
138
+ function collectKForScopes(sourceFile, checker, ts, config) {
63
139
  const scopes = [];
64
140
  const visit = (node) => {
65
141
  if (ts.isJsxElement(node)) {
66
142
  const forAttr = getJsxAttribute(node.openingElement, config.forAttr, ts);
67
143
  if (forAttr) {
68
- const names = resolveScopeNames(node.openingElement, forAttr, config, ts);
144
+ const bindings = resolveScopeBindings(node.openingElement, forAttr, checker, config, ts);
69
145
  const start = node.openingElement.end;
70
146
  const end = node.closingElement.getStart(sourceFile);
71
- if (start < end && names.length > 0) {
72
- scopes.push({ start, end, names });
147
+ if (start < end && bindings.length > 0) {
148
+ scopes.push({ start, end, bindings });
73
149
  }
74
150
  }
75
151
  }
@@ -78,15 +154,453 @@ function collectKForScopes(sourceFile, ts, config) {
78
154
  visit(sourceFile);
79
155
  return scopes;
80
156
  }
81
- function resolveScopeNames(opening, forAttr, config, ts) {
157
+ function resolveScopeBindings(opening, forAttr, checker, config, ts) {
82
158
  const forExpression = getAttributeText(forAttr, ts);
83
159
  if (forExpression !== undefined) {
84
- const parsedNames = parseKForAliases(forExpression, config.allowOfKeyword);
85
- return parsedNames || [];
160
+ const parsed = parseKForExpression(forExpression, config.allowOfKeyword);
161
+ if (parsed) {
162
+ const sourceTypes = resolveExpressionTypesFromText(parsed.source, {
163
+ checker,
164
+ ts,
165
+ scopeNode: opening,
166
+ });
167
+ return createBindings(parsed.aliases, sourceTypes, checker, opening, ts);
168
+ }
86
169
  }
87
170
  const itemName = getScopeName(opening, config.itemAttr, config.itemName, ts);
88
171
  const indexName = getScopeName(opening, config.indexAttr, config.indexName, ts);
89
- return uniqueIdentifiers([itemName, indexName]);
172
+ const aliases = uniqueIdentifiers([itemName, indexName]);
173
+ const sourceTypes = getLegacyForSourceTypes(forAttr, checker, ts);
174
+ return createBindings(aliases, sourceTypes, checker, opening, ts);
175
+ }
176
+ function createBindings(names, sourceTypes, checker, scopeNode, ts) {
177
+ if (names.length === 0) {
178
+ return [];
179
+ }
180
+ const inferred = inferBindingTypes(sourceTypes, names.length, checker, scopeNode, ts);
181
+ const bindings = [];
182
+ for (let i = 0; i < names.length; i++) {
183
+ bindings.push({
184
+ name: names[i],
185
+ types: inferred[i] || [],
186
+ });
187
+ }
188
+ return bindings;
189
+ }
190
+ function inferBindingTypes(sourceTypes, bindingCount, checker, scopeNode, ts) {
191
+ const slots = Array.from({ length: bindingCount }, () => []);
192
+ const candidates = expandUnionTypes(sourceTypes, ts);
193
+ for (let i = 0; i < candidates.length; i++) {
194
+ const sourceType = checker.getApparentType(candidates[i]);
195
+ const elementType = checker.getIndexTypeOfType(sourceType, ts.IndexKind.Number);
196
+ const stringValueType = elementType ? undefined : checker.getIndexTypeOfType(sourceType, ts.IndexKind.String);
197
+ const valueTypes = elementType ? [elementType] : stringValueType ? [stringValueType] : [];
198
+ if (valueTypes.length === 0) {
199
+ continue;
200
+ }
201
+ slots[0].push(...valueTypes);
202
+ if (bindingCount > 1) {
203
+ slots[1].push(elementType ? checker.getNumberType() : checker.getStringType());
204
+ }
205
+ if (bindingCount > 2) {
206
+ slots[2].push(checker.getNumberType());
207
+ }
208
+ }
209
+ for (let i = 0; i < slots.length; i++) {
210
+ slots[i] = uniqueTypes(slots[i], checker, scopeNode, ts);
211
+ }
212
+ return slots;
213
+ }
214
+ function expandUnionTypes(types, ts) {
215
+ const result = [];
216
+ for (let i = 0; i < types.length; i++) {
217
+ const type = types[i];
218
+ if (type.flags & ts.TypeFlags.Union) {
219
+ const union = type;
220
+ result.push(...union.types);
221
+ continue;
222
+ }
223
+ result.push(type);
224
+ }
225
+ return result;
226
+ }
227
+ function getLegacyForSourceTypes(forAttr, checker, ts) {
228
+ const expression = getAttributeExpression(forAttr, ts);
229
+ if (!expression || ts.isStringLiteralLike(expression)) {
230
+ return [];
231
+ }
232
+ return [checker.getTypeAtLocation(expression)];
233
+ }
234
+ function getAttributeExpression(attr, ts) {
235
+ if (!attr?.initializer || !ts.isJsxExpression(attr.initializer) || !attr.initializer.expression) {
236
+ return undefined;
237
+ }
238
+ return attr.initializer.expression;
239
+ }
240
+ function resolveExpressionTypesFromText(raw, context) {
241
+ const value = raw.trim();
242
+ if (!value) {
243
+ return [];
244
+ }
245
+ const sourceFile = context.ts.createSourceFile('__k_for_expression.ts', `(${value});`, context.ts.ScriptTarget.Latest, true, context.ts.ScriptKind.TS);
246
+ if (sourceFile.statements.length === 0) {
247
+ return [];
248
+ }
249
+ const statement = sourceFile.statements[0];
250
+ if (!context.ts.isExpressionStatement(statement)) {
251
+ return [];
252
+ }
253
+ const types = resolveExpressionTypes(statement.expression, context);
254
+ return uniqueTypes(types, context.checker, context.scopeNode, context.ts);
255
+ }
256
+ function resolveExpressionTypes(expr, context) {
257
+ const ts = context.ts;
258
+ const target = unwrapExpression(expr, ts);
259
+ if (ts.isIdentifier(target)) {
260
+ return resolveIdentifierTypes(target.text, context);
261
+ }
262
+ if (ts.isPropertyAccessExpression(target)) {
263
+ const objectTypes = resolveExpressionTypes(target.expression, context);
264
+ return resolvePropertyTypes(objectTypes, target.name.text, context, false);
265
+ }
266
+ if (ts.isElementAccessExpression(target)) {
267
+ const objectTypes = resolveExpressionTypes(target.expression, context);
268
+ const arg = target.argumentExpression;
269
+ if (!arg) {
270
+ return [];
271
+ }
272
+ if (ts.isStringLiteralLike(arg)) {
273
+ return resolvePropertyTypes(objectTypes, arg.text, context, true);
274
+ }
275
+ if (ts.isNumericLiteral(arg)) {
276
+ return resolveNumericElementTypes(objectTypes, Number(arg.text), context);
277
+ }
278
+ return resolveIndexedTypes(objectTypes, context);
279
+ }
280
+ if (ts.isCallExpression(target)) {
281
+ const calleeTypes = resolveExpressionTypes(target.expression, context);
282
+ const result = [];
283
+ for (let i = 0; i < calleeTypes.length; i++) {
284
+ const signatures = context.checker.getSignaturesOfType(calleeTypes[i], ts.SignatureKind.Call);
285
+ for (let j = 0; j < signatures.length; j++) {
286
+ result.push(context.checker.getReturnTypeOfSignature(signatures[j]));
287
+ }
288
+ }
289
+ return uniqueTypes(result, context.checker, context.scopeNode, ts);
290
+ }
291
+ if (ts.isConditionalExpression(target)) {
292
+ const left = resolveExpressionTypes(target.whenTrue, context);
293
+ const right = resolveExpressionTypes(target.whenFalse, context);
294
+ return uniqueTypes([...left, ...right], context.checker, context.scopeNode, ts);
295
+ }
296
+ if (ts.isBinaryExpression(target) &&
297
+ (target.operatorToken.kind === ts.SyntaxKind.BarBarToken ||
298
+ target.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken ||
299
+ target.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken)) {
300
+ const left = resolveExpressionTypes(target.left, context);
301
+ const right = resolveExpressionTypes(target.right, context);
302
+ return uniqueTypes([...left, ...right], context.checker, context.scopeNode, ts);
303
+ }
304
+ return [];
305
+ }
306
+ function unwrapExpression(expr, ts) {
307
+ let current = expr;
308
+ while (true) {
309
+ if (ts.isParenthesizedExpression(current)) {
310
+ current = current.expression;
311
+ continue;
312
+ }
313
+ if (ts.isAsExpression(current) || ts.isTypeAssertionExpression(current) || ts.isSatisfiesExpression(current)) {
314
+ current = current.expression;
315
+ continue;
316
+ }
317
+ if (ts.isNonNullExpression(current)) {
318
+ current = current.expression;
319
+ continue;
320
+ }
321
+ return current;
322
+ }
323
+ }
324
+ function resolveIdentifierTypes(name, context) {
325
+ const localTypes = context.localBindings?.get(name);
326
+ if (localTypes && localTypes.length > 0) {
327
+ return [...localTypes];
328
+ }
329
+ const symbol = resolveSymbolInScope(name, context);
330
+ if (!symbol) {
331
+ return [];
332
+ }
333
+ return [context.checker.getTypeOfSymbolAtLocation(symbol, context.scopeNode)];
334
+ }
335
+ function resolveSymbolInScope(name, context) {
336
+ const symbols = context.checker.getSymbolsInScope(context.scopeNode, context.ts.SymbolFlags.Value | context.ts.SymbolFlags.Alias);
337
+ for (let i = 0; i < symbols.length; i++) {
338
+ const symbol = symbols[i];
339
+ if (symbol.getName() !== name) {
340
+ continue;
341
+ }
342
+ if (symbol.flags & context.ts.SymbolFlags.Alias) {
343
+ const aliased = context.checker.getAliasedSymbol(symbol);
344
+ if (aliased.flags & context.ts.SymbolFlags.Value) {
345
+ return aliased;
346
+ }
347
+ continue;
348
+ }
349
+ if (symbol.flags & context.ts.SymbolFlags.Value) {
350
+ return symbol;
351
+ }
352
+ }
353
+ return undefined;
354
+ }
355
+ function resolvePropertyTypes(objectTypes, propertyName, context, allowStringIndexFallback) {
356
+ const result = [];
357
+ for (let i = 0; i < objectTypes.length; i++) {
358
+ const targetType = context.checker.getApparentType(objectTypes[i]);
359
+ const prop = context.checker.getPropertyOfType(targetType, propertyName);
360
+ if (prop) {
361
+ result.push(context.checker.getTypeOfSymbolAtLocation(prop, context.scopeNode));
362
+ continue;
363
+ }
364
+ if (allowStringIndexFallback) {
365
+ const stringIndexType = context.checker.getIndexTypeOfType(targetType, context.ts.IndexKind.String);
366
+ if (stringIndexType) {
367
+ result.push(stringIndexType);
368
+ }
369
+ }
370
+ }
371
+ return uniqueTypes(result, context.checker, context.scopeNode, context.ts);
372
+ }
373
+ function resolveNumericElementTypes(objectTypes, index, context) {
374
+ const result = [];
375
+ const indexName = String(index);
376
+ for (let i = 0; i < objectTypes.length; i++) {
377
+ const targetType = context.checker.getApparentType(objectTypes[i]);
378
+ const property = context.checker.getPropertyOfType(targetType, indexName);
379
+ if (property) {
380
+ result.push(context.checker.getTypeOfSymbolAtLocation(property, context.scopeNode));
381
+ }
382
+ const numericIndexType = context.checker.getIndexTypeOfType(targetType, context.ts.IndexKind.Number);
383
+ if (numericIndexType) {
384
+ result.push(numericIndexType);
385
+ }
386
+ }
387
+ return uniqueTypes(result, context.checker, context.scopeNode, context.ts);
388
+ }
389
+ function resolveIndexedTypes(objectTypes, context) {
390
+ const result = [];
391
+ for (let i = 0; i < objectTypes.length; i++) {
392
+ const targetType = context.checker.getApparentType(objectTypes[i]);
393
+ const stringIndexType = context.checker.getIndexTypeOfType(targetType, context.ts.IndexKind.String);
394
+ if (stringIndexType) {
395
+ result.push(stringIndexType);
396
+ }
397
+ const numericIndexType = context.checker.getIndexTypeOfType(targetType, context.ts.IndexKind.Number);
398
+ if (numericIndexType) {
399
+ result.push(numericIndexType);
400
+ }
401
+ }
402
+ return uniqueTypes(result, context.checker, context.scopeNode, context.ts);
403
+ }
404
+ function createMemberCompletionEntries(types, prefix, checker, scopeNode, ts) {
405
+ if (types.length === 0) {
406
+ return [];
407
+ }
408
+ const entries = new Map();
409
+ const typeCandidates = uniqueTypes(types, checker, scopeNode, ts);
410
+ for (let i = 0; i < typeCandidates.length; i++) {
411
+ const props = checker.getPropertiesOfType(checker.getApparentType(typeCandidates[i]));
412
+ for (let j = 0; j < props.length; j++) {
413
+ const prop = props[j];
414
+ const name = prop.getName();
415
+ if (!isValidIdentifier(name) || name.startsWith('__@')) {
416
+ continue;
417
+ }
418
+ if (prefix && !name.startsWith(prefix)) {
419
+ continue;
420
+ }
421
+ if (entries.has(name)) {
422
+ continue;
423
+ }
424
+ entries.set(name, {
425
+ name,
426
+ kind: getCompletionKind(prop, ts),
427
+ kindModifiers: '',
428
+ sortText: '0',
429
+ });
430
+ }
431
+ }
432
+ return Array.from(entries.values()).sort((a, b) => a.name.localeCompare(b.name));
433
+ }
434
+ function createAliasCompletionEntries(bindings, text, position, ts) {
435
+ const prefix = getIdentifierPrefix(text, position);
436
+ if (prefix.isMemberAccess) {
437
+ return [];
438
+ }
439
+ const entries = [];
440
+ for (const binding of bindings.values()) {
441
+ if (prefix.text && !binding.name.startsWith(prefix.text)) {
442
+ continue;
443
+ }
444
+ entries.push({
445
+ name: binding.name,
446
+ kind: ts.ScriptElementKind.localVariableElement,
447
+ kindModifiers: '',
448
+ sortText: '0',
449
+ });
450
+ }
451
+ return entries;
452
+ }
453
+ function getCompletionKind(symbol, ts) {
454
+ const flags = symbol.flags;
455
+ if (flags & ts.SymbolFlags.Method) {
456
+ return ts.ScriptElementKind.memberFunctionElement;
457
+ }
458
+ if (flags & ts.SymbolFlags.Function) {
459
+ return ts.ScriptElementKind.functionElement;
460
+ }
461
+ if (flags & (ts.SymbolFlags.GetAccessor | ts.SymbolFlags.SetAccessor | ts.SymbolFlags.Property)) {
462
+ return ts.ScriptElementKind.memberVariableElement;
463
+ }
464
+ return ts.ScriptElementKind.memberVariableElement;
465
+ }
466
+ function mergeCompletionInfo(completions, extraEntries, forceMemberCompletion) {
467
+ if (!completions) {
468
+ return {
469
+ isGlobalCompletion: !forceMemberCompletion,
470
+ isMemberCompletion: forceMemberCompletion,
471
+ isNewIdentifierLocation: !forceMemberCompletion,
472
+ entries: extraEntries,
473
+ };
474
+ }
475
+ const seen = new Set();
476
+ for (let i = 0; i < completions.entries.length; i++) {
477
+ seen.add(completions.entries[i].name);
478
+ }
479
+ const merged = completions.entries.slice();
480
+ for (let i = 0; i < extraEntries.length; i++) {
481
+ const entry = extraEntries[i];
482
+ if (seen.has(entry.name)) {
483
+ continue;
484
+ }
485
+ seen.add(entry.name);
486
+ merged.push(entry);
487
+ }
488
+ return {
489
+ ...completions,
490
+ isMemberCompletion: completions.isMemberCompletion || forceMemberCompletion,
491
+ entries: merged,
492
+ };
493
+ }
494
+ function createBindingTypeMap(bindings) {
495
+ const map = new Map();
496
+ for (const binding of bindings.values()) {
497
+ map.set(binding.name, binding.types);
498
+ }
499
+ return map;
500
+ }
501
+ function getMemberCompletionContext(text, position) {
502
+ const sliceStart = Math.max(0, position - 200);
503
+ const snippet = text.slice(sliceStart, position);
504
+ const match = MEMBER_COMPLETION_PATTERN.exec(snippet);
505
+ if (!match) {
506
+ return undefined;
507
+ }
508
+ return {
509
+ receiver: match[1],
510
+ prefix: match[2] || '',
511
+ };
512
+ }
513
+ function getIdentifierPrefix(text, position) {
514
+ let start = position;
515
+ while (start > 0 && isIdentifierPart(text.charCodeAt(start - 1))) {
516
+ start--;
517
+ }
518
+ const prefix = text.slice(start, position);
519
+ const isMemberAccess = start > 0 && text.charCodeAt(start - 1) === 46;
520
+ return { text: prefix, isMemberAccess };
521
+ }
522
+ function isIdentifierPart(code) {
523
+ return ((code >= 65 && code <= 90) ||
524
+ (code >= 97 && code <= 122) ||
525
+ (code >= 48 && code <= 57) ||
526
+ code === 95 ||
527
+ code === 36);
528
+ }
529
+ function findIdentifierAtPosition(sourceFile, position, ts) {
530
+ const exact = findInnermostNode(sourceFile, normalizePosition(position, sourceFile), ts);
531
+ if (exact && ts.isIdentifier(exact)) {
532
+ return exact;
533
+ }
534
+ if (position > 0) {
535
+ const prev = findInnermostNode(sourceFile, normalizePosition(position - 1, sourceFile), ts);
536
+ if (prev && ts.isIdentifier(prev)) {
537
+ return prev;
538
+ }
539
+ }
540
+ return undefined;
541
+ }
542
+ function normalizePosition(position, sourceFile) {
543
+ const max = Math.max(sourceFile.text.length - 1, 0);
544
+ if (position < 0) {
545
+ return 0;
546
+ }
547
+ if (position > max) {
548
+ return max;
549
+ }
550
+ return position;
551
+ }
552
+ function findInnermostNode(node, position, ts) {
553
+ if (position < node.getFullStart() || position >= node.end) {
554
+ return undefined;
555
+ }
556
+ let match;
557
+ ts.forEachChild(node, (child) => {
558
+ const childMatch = findInnermostNode(child, position, ts);
559
+ if (childMatch) {
560
+ match = childMatch;
561
+ }
562
+ });
563
+ return match || node;
564
+ }
565
+ function collectBindingsAtPosition(position, scopes) {
566
+ const bindings = new Map();
567
+ for (let i = scopes.length - 1; i >= 0; i--) {
568
+ const scope = scopes[i];
569
+ if (position < scope.start || position >= scope.end) {
570
+ continue;
571
+ }
572
+ for (let j = 0; j < scope.bindings.length; j++) {
573
+ const binding = scope.bindings[j];
574
+ if (!bindings.has(binding.name)) {
575
+ bindings.set(binding.name, binding);
576
+ }
577
+ }
578
+ }
579
+ return bindings;
580
+ }
581
+ function formatTypeList(types, checker, scopeNode, ts) {
582
+ if (types.length === 0) {
583
+ return 'any';
584
+ }
585
+ const texts = new Set();
586
+ for (let i = 0; i < types.length; i++) {
587
+ texts.add(checker.typeToString(types[i], scopeNode, ts.TypeFormatFlags.NoTruncation));
588
+ }
589
+ return Array.from(texts).join(' | ');
590
+ }
591
+ function uniqueTypes(types, checker, scopeNode, ts) {
592
+ const seen = new Set();
593
+ const result = [];
594
+ for (let i = 0; i < types.length; i++) {
595
+ const type = types[i];
596
+ const text = checker.typeToString(type, scopeNode, ts.TypeFormatFlags.NoTruncation);
597
+ if (seen.has(text)) {
598
+ continue;
599
+ }
600
+ seen.add(text);
601
+ result.push(type);
602
+ }
603
+ return result;
90
604
  }
91
605
  function getScopeName(opening, attrName, fallback, ts) {
92
606
  const attr = getJsxAttribute(opening, attrName, ts);
@@ -140,13 +654,15 @@ function isSuppressed(position, diagnosticName, scopes) {
140
654
  if (position < scope.start || position >= scope.end) {
141
655
  continue;
142
656
  }
143
- if (scope.names.includes(diagnosticName)) {
144
- return true;
657
+ for (let j = 0; j < scope.bindings.length; j++) {
658
+ if (scope.bindings[j].name === diagnosticName) {
659
+ return true;
660
+ }
145
661
  }
146
662
  }
147
663
  return false;
148
664
  }
149
- function parseKForAliases(raw, allowOfKeyword) {
665
+ function parseKForExpression(raw, allowOfKeyword) {
150
666
  const value = raw.trim();
151
667
  if (!value) {
152
668
  return null;
@@ -158,7 +674,10 @@ function parseKForAliases(raw, allowOfKeyword) {
158
674
  if ((!allowOfKeyword && keyword === 'of') || !source) {
159
675
  return null;
160
676
  }
161
- return uniqueIdentifiers([tupleMatch[1], tupleMatch[2], tupleMatch[3]].filter(Boolean));
677
+ return {
678
+ aliases: uniqueIdentifiers([tupleMatch[1], tupleMatch[2], tupleMatch[3]].filter(Boolean)),
679
+ source,
680
+ };
162
681
  }
163
682
  const singleMatch = KFOR_SINGLE_PATTERN.exec(value);
164
683
  if (singleMatch) {
@@ -167,7 +686,10 @@ function parseKForAliases(raw, allowOfKeyword) {
167
686
  if ((!allowOfKeyword && keyword === 'of') || !source) {
168
687
  return null;
169
688
  }
170
- return uniqueIdentifiers([singleMatch[1]]);
689
+ return {
690
+ aliases: uniqueIdentifiers([singleMatch[1]]),
691
+ source,
692
+ };
171
693
  }
172
694
  return null;
173
695
  }
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@ktjs/ts-plugin",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "TypeScript language service plugin for kt.js k-for scope names in TSX.",
5
5
  "type": "commonjs",
6
- "main": "./dist/index.cjs",
7
- "types": "./dist/index.d.cts",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
8
  "exports": {
9
9
  ".": {
10
- "types": "./dist/index.d.cts",
11
- "require": "./dist/index.cjs",
12
- "default": "./dist/index.cjs"
10
+ "types": "./dist/index.d.ts",
11
+ "require": "./dist/index.js",
12
+ "default": "./dist/index.js"
13
13
  }
14
14
  },
15
15
  "files": [
@@ -39,4 +39,4 @@
39
39
  "scripts": {
40
40
  "build": "tsc -p tsconfig.json"
41
41
  }
42
- }
42
+ }