@ktjs/ts-plugin 0.1.1 → 0.1.3

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/dist/index.d.ts CHANGED
@@ -1 +1,7 @@
1
- export { }
1
+ import type tsModule from 'typescript/lib/tsserverlibrary';
2
+ declare function init(modules: {
3
+ typescript: typeof tsModule;
4
+ }): {
5
+ create: (info: tsModule.server.PluginCreateInfo) => tsModule.LanguageService;
6
+ };
7
+ export = init;
package/dist/index.js ADDED
@@ -0,0 +1,190 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ const node_path_1 = __importDefault(require("node:path"));
6
+ const DIAGNOSTIC_CANNOT_FIND_NAME = 2304;
7
+ const IDENTIFIER_RE = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
8
+ const KFOR_SINGLE_PATTERN = /^([A-Za-z_$][A-Za-z0-9_$]*)\s+(in|of)\s+([\s\S]+)$/;
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
+ function init(modules) {
11
+ const ts = modules.typescript;
12
+ function create(info) {
13
+ const languageService = info.languageService;
14
+ const config = resolveConfig(info.config);
15
+ const proxy = Object.create(null);
16
+ for (const key of Object.keys(languageService)) {
17
+ const member = languageService[key];
18
+ proxy[key] = (...args) => member.apply(languageService, args);
19
+ }
20
+ proxy.getSemanticDiagnostics = (fileName) => {
21
+ const diagnostics = languageService.getSemanticDiagnostics(fileName);
22
+ if (!isJsxLikeFile(fileName)) {
23
+ return diagnostics;
24
+ }
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) {
31
+ return diagnostics;
32
+ }
33
+ return diagnostics.filter((diag) => {
34
+ if (diag.code !== DIAGNOSTIC_CANNOT_FIND_NAME || diag.start == null || diag.length == null) {
35
+ return true;
36
+ }
37
+ const name = sourceFile.text.slice(diag.start, diag.start + diag.length).trim();
38
+ if (!isValidIdentifier(name)) {
39
+ return true;
40
+ }
41
+ return !isSuppressed(diag.start, name, scopes);
42
+ });
43
+ };
44
+ return proxy;
45
+ }
46
+ return { create };
47
+ }
48
+ function resolveConfig(config) {
49
+ return {
50
+ forAttr: config?.forAttr || 'k-for',
51
+ itemAttr: config?.itemAttr || 'k-for-item',
52
+ indexAttr: config?.indexAttr || 'k-for-index',
53
+ itemName: config?.itemName || 'item',
54
+ indexName: config?.indexName || 'index',
55
+ allowOfKeyword: config?.allowOfKeyword !== false,
56
+ };
57
+ }
58
+ function isJsxLikeFile(fileName) {
59
+ const ext = node_path_1.default.extname(fileName).toLowerCase();
60
+ return ext === '.tsx' || ext === '.jsx';
61
+ }
62
+ function collectKForScopes(sourceFile, ts, config) {
63
+ const scopes = [];
64
+ const visit = (node) => {
65
+ if (ts.isJsxElement(node)) {
66
+ const forAttr = getJsxAttribute(node.openingElement, config.forAttr, ts);
67
+ if (forAttr) {
68
+ const names = resolveScopeNames(node.openingElement, forAttr, config, ts);
69
+ const start = node.openingElement.end;
70
+ const end = node.closingElement.getStart(sourceFile);
71
+ if (start < end && names.length > 0) {
72
+ scopes.push({ start, end, names });
73
+ }
74
+ }
75
+ }
76
+ ts.forEachChild(node, visit);
77
+ };
78
+ visit(sourceFile);
79
+ return scopes;
80
+ }
81
+ function resolveScopeNames(opening, forAttr, config, ts) {
82
+ const forExpression = getAttributeText(forAttr, ts);
83
+ if (forExpression !== undefined) {
84
+ const parsedNames = parseKForAliases(forExpression, config.allowOfKeyword);
85
+ return parsedNames || [];
86
+ }
87
+ const itemName = getScopeName(opening, config.itemAttr, config.itemName, ts);
88
+ const indexName = getScopeName(opening, config.indexAttr, config.indexName, ts);
89
+ return uniqueIdentifiers([itemName, indexName]);
90
+ }
91
+ function getScopeName(opening, attrName, fallback, ts) {
92
+ const attr = getJsxAttribute(opening, attrName, ts);
93
+ const raw = getAttributeText(attr, ts, true);
94
+ if (raw && isValidIdentifier(raw)) {
95
+ return raw;
96
+ }
97
+ return fallback;
98
+ }
99
+ function getJsxAttribute(opening, attrName, ts) {
100
+ const attrs = opening.attributes.properties;
101
+ for (let i = 0; i < attrs.length; i++) {
102
+ const attr = attrs[i];
103
+ if (!ts.isJsxAttribute(attr)) {
104
+ continue;
105
+ }
106
+ if (getAttributeName(attr.name) === attrName) {
107
+ return attr;
108
+ }
109
+ }
110
+ return undefined;
111
+ }
112
+ function getAttributeText(attr, ts, allowIdentifier = false) {
113
+ if (!attr || !attr.initializer) {
114
+ return undefined;
115
+ }
116
+ if (ts.isStringLiteral(attr.initializer)) {
117
+ return attr.initializer.text;
118
+ }
119
+ if (!ts.isJsxExpression(attr.initializer) || !attr.initializer.expression) {
120
+ return undefined;
121
+ }
122
+ const expr = attr.initializer.expression;
123
+ if (ts.isStringLiteralLike(expr)) {
124
+ return expr.text;
125
+ }
126
+ if (allowIdentifier && ts.isIdentifier(expr)) {
127
+ return expr.text;
128
+ }
129
+ return undefined;
130
+ }
131
+ function getAttributeName(name) {
132
+ if ('text' in name) {
133
+ return String(name.text);
134
+ }
135
+ return name.getText();
136
+ }
137
+ function isSuppressed(position, diagnosticName, scopes) {
138
+ for (let i = scopes.length - 1; i >= 0; i--) {
139
+ const scope = scopes[i];
140
+ if (position < scope.start || position >= scope.end) {
141
+ continue;
142
+ }
143
+ if (scope.names.includes(diagnosticName)) {
144
+ return true;
145
+ }
146
+ }
147
+ return false;
148
+ }
149
+ function parseKForAliases(raw, allowOfKeyword) {
150
+ const value = raw.trim();
151
+ if (!value) {
152
+ return null;
153
+ }
154
+ const tupleMatch = KFOR_TUPLE_PATTERN.exec(value);
155
+ if (tupleMatch) {
156
+ const keyword = tupleMatch[4];
157
+ const source = tupleMatch[5]?.trim();
158
+ if ((!allowOfKeyword && keyword === 'of') || !source) {
159
+ return null;
160
+ }
161
+ return uniqueIdentifiers([tupleMatch[1], tupleMatch[2], tupleMatch[3]].filter(Boolean));
162
+ }
163
+ const singleMatch = KFOR_SINGLE_PATTERN.exec(value);
164
+ if (singleMatch) {
165
+ const keyword = singleMatch[2];
166
+ const source = singleMatch[3]?.trim();
167
+ if ((!allowOfKeyword && keyword === 'of') || !source) {
168
+ return null;
169
+ }
170
+ return uniqueIdentifiers([singleMatch[1]]);
171
+ }
172
+ return null;
173
+ }
174
+ function isValidIdentifier(name) {
175
+ return IDENTIFIER_RE.test(name);
176
+ }
177
+ function uniqueIdentifiers(names) {
178
+ const seen = new Set();
179
+ const result = [];
180
+ for (let i = 0; i < names.length; i++) {
181
+ const name = names[i];
182
+ if (!isValidIdentifier(name) || seen.has(name)) {
183
+ continue;
184
+ }
185
+ seen.add(name);
186
+ result.push(name);
187
+ }
188
+ return result;
189
+ }
190
+ module.exports = init;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ktjs/ts-plugin",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "TypeScript language service plugin for kt.js k-for scope names in TSX.",
5
5
  "type": "commonjs",
6
6
  "main": "./dist/index.cjs",
package/dist/index.mjs DELETED
@@ -1 +0,0 @@
1
-