@player-tools/typescript-expression-plugin 0.2.2--canary.20.454

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.
@@ -0,0 +1,210 @@
1
+ 'use strict';
2
+
3
+ var typescriptTemplateLanguageServiceDecorator = require('typescript-template-language-service-decorator');
4
+ var ts = require('typescript/lib/tsserverlibrary');
5
+ var player = require('@player-ui/player');
6
+
7
+ function _interopNamespace(e) {
8
+ if (e && e.__esModule) return e;
9
+ var n = Object.create(null);
10
+ if (e) {
11
+ Object.keys(e).forEach(function (k) {
12
+ if (k !== 'default') {
13
+ var d = Object.getOwnPropertyDescriptor(e, k);
14
+ Object.defineProperty(n, k, d.get ? d : {
15
+ enumerable: true,
16
+ get: function () { return e[k]; }
17
+ });
18
+ }
19
+ });
20
+ }
21
+ n["default"] = e;
22
+ return Object.freeze(n);
23
+ }
24
+
25
+ var ts__namespace = /*#__PURE__*/_interopNamespace(ts);
26
+
27
+ function isInRange(position, location) {
28
+ return position.character >= location.start.character && position.character <= location.end.character;
29
+ }
30
+ function getTokenAtPosition(node, position) {
31
+ var _a;
32
+ if (node.type === "CallExpression") {
33
+ const anyArgs = node.args.find((arg) => {
34
+ return getTokenAtPosition(arg, position);
35
+ });
36
+ if (anyArgs) {
37
+ return anyArgs;
38
+ }
39
+ const asTarget = getTokenAtPosition(node.callTarget, position);
40
+ if (asTarget) {
41
+ return asTarget;
42
+ }
43
+ }
44
+ if (node.type === "Assignment") {
45
+ const asTarget = (_a = getTokenAtPosition(node.left, position)) != null ? _a : getTokenAtPosition(node.right, position);
46
+ if (asTarget) {
47
+ return asTarget;
48
+ }
49
+ }
50
+ if (node.location && isInRange(position, node.location)) {
51
+ return node;
52
+ }
53
+ }
54
+
55
+ class ExpressionLanguageService {
56
+ constructor(options) {
57
+ this._expressions = new Map();
58
+ this.logger = options == null ? void 0 : options.logger;
59
+ this.setExpressions(new Map([
60
+ [
61
+ "test",
62
+ {
63
+ name: "test",
64
+ description: "test expression",
65
+ args: []
66
+ }
67
+ ],
68
+ [
69
+ "foo",
70
+ {
71
+ name: "foo",
72
+ description: "Test foo expression",
73
+ args: [
74
+ {
75
+ name: "path",
76
+ type: "string"
77
+ }
78
+ ]
79
+ }
80
+ ]
81
+ ]));
82
+ }
83
+ setExpressions(expressionData) {
84
+ this._expressions = expressionData;
85
+ }
86
+ getCompletionsAtPosition(context, position) {
87
+ var _a, _b, _c;
88
+ const line = context.text.split(/\n/g)[position.line];
89
+ (_a = this.logger) == null ? void 0 : _a.log(`[expression-LSP] getCompletionsAtPosition: ${line} -- ${context.rawText} -- ${context.text}`);
90
+ const parsed = player.parseExpression(line, { strict: false });
91
+ const token = getTokenAtPosition(parsed, position);
92
+ const completionInfo = {
93
+ isGlobalCompletion: false,
94
+ isMemberCompletion: false,
95
+ isNewIdentifierLocation: false,
96
+ entries: []
97
+ };
98
+ if ((token == null ? void 0 : token.type) === "Identifier") {
99
+ const start = (_c = (_b = token.location) == null ? void 0 : _b.start) != null ? _c : { character: 0 };
100
+ const wordFromStart = line.slice(start.character, position.character);
101
+ const allCompletions = Array.from(this._expressions.keys()).filter((key) => key.startsWith(wordFromStart));
102
+ allCompletions.forEach((c) => {
103
+ completionInfo.entries.push({
104
+ name: c,
105
+ kind: ts__namespace.ScriptElementKind.functionElement,
106
+ sortText: c,
107
+ isRecommended: true
108
+ });
109
+ });
110
+ }
111
+ return completionInfo;
112
+ }
113
+ getQuickInfoAtPosition(context, position) {
114
+ var _a, _b, _c, _d, _e;
115
+ (_a = this.logger) == null ? void 0 : _a.log(`getCompletionsAtPosition: ${context.text}`);
116
+ const parsed = player.parseExpression(context.text, { strict: false });
117
+ const token = getTokenAtPosition(parsed, position);
118
+ if ((token == null ? void 0 : token.type) === "Identifier") {
119
+ const expression = this._expressions.get(token.name);
120
+ if (expression) {
121
+ return {
122
+ textSpan: {
123
+ start: (_c = (_b = token.location) == null ? void 0 : _b.start.character) != null ? _c : 0,
124
+ length: (_e = (_d = token.location) == null ? void 0 : _d.end.character) != null ? _e : 0
125
+ },
126
+ kindModifiers: ts__namespace.ScriptElementKindModifier.none,
127
+ kind: ts__namespace.ScriptElementKind.functionElement,
128
+ documentation: [
129
+ {
130
+ kind: "text",
131
+ text: expression.description
132
+ }
133
+ ]
134
+ };
135
+ }
136
+ }
137
+ return void 0;
138
+ }
139
+ getCompletionEntryDetails(context, position, name) {
140
+ var _a, _b;
141
+ const expression = this._expressions.get(name);
142
+ const prefix = [
143
+ {
144
+ text: ", ",
145
+ kind: ts__namespace.ScriptElementKind.unknown
146
+ }
147
+ ];
148
+ const completionDetails = {
149
+ name,
150
+ kind: ts__namespace.ScriptElementKind.functionElement,
151
+ kindModifiers: ts__namespace.ScriptElementKindModifier.none,
152
+ documentation: [
153
+ {
154
+ kind: "text",
155
+ text: (_a = expression == null ? void 0 : expression.description) != null ? _a : "Some description"
156
+ }
157
+ ],
158
+ displayParts: [
159
+ {
160
+ text: name,
161
+ kind: ts__namespace.ScriptElementKind.functionElement
162
+ },
163
+ {
164
+ text: "(",
165
+ kind: ts__namespace.ScriptElementKind.unknown
166
+ },
167
+ ...(_b = expression == null ? void 0 : expression.args.flatMap((arg, index) => [
168
+ ...index === 0 ? [] : prefix,
169
+ {
170
+ text: arg.name,
171
+ kind: ts__namespace.ScriptElementKind.parameterElement
172
+ },
173
+ {
174
+ text: ": ",
175
+ kind: ts__namespace.ScriptElementKind.unknown
176
+ },
177
+ {
178
+ text: arg.type,
179
+ kind: ts__namespace.ScriptElementKind.typeParameterElement
180
+ }
181
+ ])) != null ? _b : [],
182
+ {
183
+ text: ")",
184
+ kind: ts__namespace.ScriptElementKind.unknown
185
+ }
186
+ ]
187
+ };
188
+ return completionDetails;
189
+ }
190
+ }
191
+
192
+ class LSPLogger {
193
+ constructor(info) {
194
+ this.info = info;
195
+ }
196
+ log(msg) {
197
+ this.info.project.projectService.logger.info(`[player-expr-lsp] ${msg}`);
198
+ }
199
+ }
200
+
201
+ module.exports = (mod) => {
202
+ return {
203
+ create(info) {
204
+ const logger = new LSPLogger(info);
205
+ const templateService = new ExpressionLanguageService({ logger });
206
+ return typescriptTemplateLanguageServiceDecorator.decorateWithTemplateLanguageService(mod.typescript, info.languageService, info.project, templateService, { tags: ["e", "expr"], enableForStringWithSubstitutions: true }, { logger });
207
+ }
208
+ };
209
+ };
210
+ //# sourceMappingURL=index.cjs.js.map
@@ -0,0 +1,9 @@
1
+ import * as ts from 'typescript/lib/tsserverlibrary';
2
+
3
+ declare const _default: (mod: {
4
+ typescript: typeof ts;
5
+ }) => {
6
+ create(info: ts.server.PluginCreateInfo): ts.LanguageService;
7
+ };
8
+
9
+ export { _default as default };
@@ -0,0 +1,188 @@
1
+ import { decorateWithTemplateLanguageService } from 'typescript-template-language-service-decorator';
2
+ import * as ts from 'typescript/lib/tsserverlibrary';
3
+ import { parseExpression } from '@player-ui/player';
4
+
5
+ function isInRange(position, location) {
6
+ return position.character >= location.start.character && position.character <= location.end.character;
7
+ }
8
+ function getTokenAtPosition(node, position) {
9
+ var _a;
10
+ if (node.type === "CallExpression") {
11
+ const anyArgs = node.args.find((arg) => {
12
+ return getTokenAtPosition(arg, position);
13
+ });
14
+ if (anyArgs) {
15
+ return anyArgs;
16
+ }
17
+ const asTarget = getTokenAtPosition(node.callTarget, position);
18
+ if (asTarget) {
19
+ return asTarget;
20
+ }
21
+ }
22
+ if (node.type === "Assignment") {
23
+ const asTarget = (_a = getTokenAtPosition(node.left, position)) != null ? _a : getTokenAtPosition(node.right, position);
24
+ if (asTarget) {
25
+ return asTarget;
26
+ }
27
+ }
28
+ if (node.location && isInRange(position, node.location)) {
29
+ return node;
30
+ }
31
+ }
32
+
33
+ class ExpressionLanguageService {
34
+ constructor(options) {
35
+ this._expressions = new Map();
36
+ this.logger = options == null ? void 0 : options.logger;
37
+ this.setExpressions(new Map([
38
+ [
39
+ "test",
40
+ {
41
+ name: "test",
42
+ description: "test expression",
43
+ args: []
44
+ }
45
+ ],
46
+ [
47
+ "foo",
48
+ {
49
+ name: "foo",
50
+ description: "Test foo expression",
51
+ args: [
52
+ {
53
+ name: "path",
54
+ type: "string"
55
+ }
56
+ ]
57
+ }
58
+ ]
59
+ ]));
60
+ }
61
+ setExpressions(expressionData) {
62
+ this._expressions = expressionData;
63
+ }
64
+ getCompletionsAtPosition(context, position) {
65
+ var _a, _b, _c;
66
+ const line = context.text.split(/\n/g)[position.line];
67
+ (_a = this.logger) == null ? void 0 : _a.log(`[expression-LSP] getCompletionsAtPosition: ${line} -- ${context.rawText} -- ${context.text}`);
68
+ const parsed = parseExpression(line, { strict: false });
69
+ const token = getTokenAtPosition(parsed, position);
70
+ const completionInfo = {
71
+ isGlobalCompletion: false,
72
+ isMemberCompletion: false,
73
+ isNewIdentifierLocation: false,
74
+ entries: []
75
+ };
76
+ if ((token == null ? void 0 : token.type) === "Identifier") {
77
+ const start = (_c = (_b = token.location) == null ? void 0 : _b.start) != null ? _c : { character: 0 };
78
+ const wordFromStart = line.slice(start.character, position.character);
79
+ const allCompletions = Array.from(this._expressions.keys()).filter((key) => key.startsWith(wordFromStart));
80
+ allCompletions.forEach((c) => {
81
+ completionInfo.entries.push({
82
+ name: c,
83
+ kind: ts.ScriptElementKind.functionElement,
84
+ sortText: c,
85
+ isRecommended: true
86
+ });
87
+ });
88
+ }
89
+ return completionInfo;
90
+ }
91
+ getQuickInfoAtPosition(context, position) {
92
+ var _a, _b, _c, _d, _e;
93
+ (_a = this.logger) == null ? void 0 : _a.log(`getCompletionsAtPosition: ${context.text}`);
94
+ const parsed = parseExpression(context.text, { strict: false });
95
+ const token = getTokenAtPosition(parsed, position);
96
+ if ((token == null ? void 0 : token.type) === "Identifier") {
97
+ const expression = this._expressions.get(token.name);
98
+ if (expression) {
99
+ return {
100
+ textSpan: {
101
+ start: (_c = (_b = token.location) == null ? void 0 : _b.start.character) != null ? _c : 0,
102
+ length: (_e = (_d = token.location) == null ? void 0 : _d.end.character) != null ? _e : 0
103
+ },
104
+ kindModifiers: ts.ScriptElementKindModifier.none,
105
+ kind: ts.ScriptElementKind.functionElement,
106
+ documentation: [
107
+ {
108
+ kind: "text",
109
+ text: expression.description
110
+ }
111
+ ]
112
+ };
113
+ }
114
+ }
115
+ return void 0;
116
+ }
117
+ getCompletionEntryDetails(context, position, name) {
118
+ var _a, _b;
119
+ const expression = this._expressions.get(name);
120
+ const prefix = [
121
+ {
122
+ text: ", ",
123
+ kind: ts.ScriptElementKind.unknown
124
+ }
125
+ ];
126
+ const completionDetails = {
127
+ name,
128
+ kind: ts.ScriptElementKind.functionElement,
129
+ kindModifiers: ts.ScriptElementKindModifier.none,
130
+ documentation: [
131
+ {
132
+ kind: "text",
133
+ text: (_a = expression == null ? void 0 : expression.description) != null ? _a : "Some description"
134
+ }
135
+ ],
136
+ displayParts: [
137
+ {
138
+ text: name,
139
+ kind: ts.ScriptElementKind.functionElement
140
+ },
141
+ {
142
+ text: "(",
143
+ kind: ts.ScriptElementKind.unknown
144
+ },
145
+ ...(_b = expression == null ? void 0 : expression.args.flatMap((arg, index) => [
146
+ ...index === 0 ? [] : prefix,
147
+ {
148
+ text: arg.name,
149
+ kind: ts.ScriptElementKind.parameterElement
150
+ },
151
+ {
152
+ text: ": ",
153
+ kind: ts.ScriptElementKind.unknown
154
+ },
155
+ {
156
+ text: arg.type,
157
+ kind: ts.ScriptElementKind.typeParameterElement
158
+ }
159
+ ])) != null ? _b : [],
160
+ {
161
+ text: ")",
162
+ kind: ts.ScriptElementKind.unknown
163
+ }
164
+ ]
165
+ };
166
+ return completionDetails;
167
+ }
168
+ }
169
+
170
+ class LSPLogger {
171
+ constructor(info) {
172
+ this.info = info;
173
+ }
174
+ log(msg) {
175
+ this.info.project.projectService.logger.info(`[player-expr-lsp] ${msg}`);
176
+ }
177
+ }
178
+
179
+ module.exports = (mod) => {
180
+ return {
181
+ create(info) {
182
+ const logger = new LSPLogger(info);
183
+ const templateService = new ExpressionLanguageService({ logger });
184
+ return decorateWithTemplateLanguageService(mod.typescript, info.languageService, info.project, templateService, { tags: ["e", "expr"], enableForStringWithSubstitutions: true }, { logger });
185
+ }
186
+ };
187
+ };
188
+ //# sourceMappingURL=index.esm.js.map
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@player-tools/typescript-expression-plugin",
3
+ "version": "0.2.2--canary.20.454",
4
+ "private": false,
5
+ "publishConfig": {
6
+ "registry": "https://registry.npmjs.org"
7
+ },
8
+ "peerDependencies": {},
9
+ "dependencies": {
10
+ "@player-ui/player": "0.3.1--canary.117.4130",
11
+ "typescript-template-language-service-decorator": "^2.3.1",
12
+ "@babel/runtime": "7.15.4"
13
+ },
14
+ "main": "dist/index.cjs.js",
15
+ "module": "dist/index.esm.js",
16
+ "typings": "dist/index.d.ts",
17
+ "sideEffects": false,
18
+ "license": "MIT",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/player-ui/tools"
22
+ },
23
+ "bugs": {
24
+ "url": "https://github.com/player-ui/tools/issues"
25
+ },
26
+ "homepage": "https://player-ui.github.io",
27
+ "contributors": [
28
+ {
29
+ "name": "Ketan Reddy",
30
+ "url": "https://github.com/KetanReddy"
31
+ }
32
+ ]
33
+ }
package/src/index.ts ADDED
@@ -0,0 +1,23 @@
1
+ import type * as ts from 'typescript/lib/tsserverlibrary';
2
+ import { decorateWithTemplateLanguageService } from 'typescript-template-language-service-decorator';
3
+ import { ExpressionLanguageService } from './service';
4
+ import { LSPLogger } from './logger';
5
+
6
+ export = (mod: { typescript: typeof ts }) => {
7
+ return {
8
+ create(info: ts.server.PluginCreateInfo): ts.LanguageService {
9
+ const logger = new LSPLogger(info);
10
+
11
+ const templateService = new ExpressionLanguageService({ logger });
12
+
13
+ return decorateWithTemplateLanguageService(
14
+ mod.typescript,
15
+ info.languageService,
16
+ info.project,
17
+ templateService,
18
+ { tags: ['e', 'expr'], enableForStringWithSubstitutions: true },
19
+ { logger }
20
+ );
21
+ },
22
+ };
23
+ };
package/src/logger.ts ADDED
@@ -0,0 +1,14 @@
1
+ import type { Logger } from 'typescript-template-language-service-decorator';
2
+ import type * as ts from 'typescript/lib/tsserverlibrary';
3
+
4
+ export class LSPLogger implements Logger {
5
+ private readonly info: ts.server.PluginCreateInfo;
6
+
7
+ constructor(info: ts.server.PluginCreateInfo) {
8
+ this.info = info;
9
+ }
10
+
11
+ log(msg: string) {
12
+ this.info.project.projectService.logger.info(`[player-expr-lsp] ${msg}`);
13
+ }
14
+ }
package/src/service.ts ADDED
@@ -0,0 +1,188 @@
1
+ import * as ts from 'typescript/lib/tsserverlibrary';
2
+ import type {
3
+ TemplateLanguageService,
4
+ TemplateContext,
5
+ Logger,
6
+ } from 'typescript-template-language-service-decorator';
7
+ import { parseExpression } from '@player-ui/player';
8
+ import { getTokenAtPosition } from './utils';
9
+
10
+ // TODO: replace this with the XLR definitions and types
11
+ interface ExprDetails {
12
+ name: string;
13
+ description: string;
14
+ args: Array<{
15
+ name: string;
16
+ type: string;
17
+ }>;
18
+ }
19
+
20
+ export class ExpressionLanguageService implements TemplateLanguageService {
21
+ private logger?: Logger;
22
+ private _expressions = new Map<string, ExprDetails>();
23
+
24
+ constructor(options?: { logger?: Logger }) {
25
+ this.logger = options?.logger;
26
+
27
+ this.setExpressions(
28
+ new Map([
29
+ [
30
+ 'test',
31
+ {
32
+ name: 'test',
33
+ description: 'test expression',
34
+ args: [],
35
+ },
36
+ ],
37
+ [
38
+ 'foo',
39
+ {
40
+ name: 'foo',
41
+ description: 'Test foo expression',
42
+ args: [
43
+ {
44
+ name: 'path',
45
+ type: 'string',
46
+ },
47
+ ],
48
+ },
49
+ ],
50
+ ])
51
+ );
52
+ }
53
+
54
+ setExpressions(expressionData: Map<string, ExprDetails>) {
55
+ this._expressions = expressionData;
56
+ }
57
+
58
+ getCompletionsAtPosition(
59
+ context: TemplateContext,
60
+ position: ts.LineAndCharacter
61
+ ): ts.CompletionInfo {
62
+ const line = context.text.split(/\n/g)[position.line];
63
+ this.logger?.log(
64
+ `[expression-LSP] getCompletionsAtPosition: ${line} -- ${context.rawText} -- ${context.text}`
65
+ );
66
+
67
+ const parsed = parseExpression(line, { strict: false });
68
+ const token = getTokenAtPosition(parsed, position);
69
+
70
+ const completionInfo: ts.CompletionInfo = {
71
+ isGlobalCompletion: false,
72
+ isMemberCompletion: false,
73
+ isNewIdentifierLocation: false,
74
+ entries: [],
75
+ };
76
+
77
+ if (token?.type === 'Identifier') {
78
+ // get the relevant start of the identifier
79
+ const start = token.location?.start ?? { character: 0 };
80
+ const wordFromStart = line.slice(start.character, position.character);
81
+ const allCompletions = Array.from(this._expressions.keys()).filter(
82
+ (key) => key.startsWith(wordFromStart)
83
+ );
84
+
85
+ allCompletions.forEach((c) => {
86
+ completionInfo.entries.push({
87
+ name: c,
88
+ kind: ts.ScriptElementKind.functionElement,
89
+ sortText: c,
90
+ isRecommended: true,
91
+ });
92
+ });
93
+ }
94
+
95
+ return completionInfo;
96
+ }
97
+
98
+ getQuickInfoAtPosition(
99
+ context: TemplateContext,
100
+ position: ts.LineAndCharacter
101
+ ): ts.QuickInfo | undefined {
102
+ this.logger?.log(`getCompletionsAtPosition: ${context.text}`);
103
+
104
+ const parsed = parseExpression(context.text, { strict: false });
105
+ const token = getTokenAtPosition(parsed, position);
106
+
107
+ if (token?.type === 'Identifier') {
108
+ const expression = this._expressions.get(token.name);
109
+ if (expression) {
110
+ return {
111
+ textSpan: {
112
+ start: token.location?.start.character ?? 0,
113
+ length: token.location?.end.character ?? 0,
114
+ },
115
+ kindModifiers: ts.ScriptElementKindModifier.none,
116
+ kind: ts.ScriptElementKind.functionElement,
117
+ documentation: [
118
+ {
119
+ kind: 'text',
120
+ text: expression.description,
121
+ },
122
+ ],
123
+ };
124
+ }
125
+ }
126
+
127
+ return undefined;
128
+ }
129
+
130
+ getCompletionEntryDetails(
131
+ context: TemplateContext,
132
+ position: ts.LineAndCharacter,
133
+ name: string
134
+ ): ts.CompletionEntryDetails {
135
+ const expression = this._expressions.get(name);
136
+
137
+ const prefix = [
138
+ {
139
+ text: ', ',
140
+ kind: ts.ScriptElementKind.unknown,
141
+ },
142
+ ];
143
+
144
+ const completionDetails: ts.CompletionEntryDetails = {
145
+ name,
146
+ kind: ts.ScriptElementKind.functionElement,
147
+ kindModifiers: ts.ScriptElementKindModifier.none,
148
+ documentation: [
149
+ {
150
+ kind: 'text',
151
+ text: expression?.description ?? 'Some description',
152
+ },
153
+ ],
154
+ displayParts: [
155
+ {
156
+ text: name,
157
+ kind: ts.ScriptElementKind.functionElement,
158
+ },
159
+ {
160
+ text: '(',
161
+ kind: ts.ScriptElementKind.unknown,
162
+ },
163
+
164
+ ...(expression?.args.flatMap((arg, index) => [
165
+ ...(index === 0 ? [] : prefix),
166
+ {
167
+ text: arg.name,
168
+ kind: ts.ScriptElementKind.parameterElement,
169
+ },
170
+ {
171
+ text: ': ',
172
+ kind: ts.ScriptElementKind.unknown,
173
+ },
174
+ {
175
+ text: arg.type,
176
+ kind: ts.ScriptElementKind.typeParameterElement,
177
+ },
178
+ ]) ?? []),
179
+ {
180
+ text: ')',
181
+ kind: ts.ScriptElementKind.unknown,
182
+ },
183
+ ],
184
+ };
185
+
186
+ return completionDetails;
187
+ }
188
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,45 @@
1
+ import type { Position } from 'vscode-languageserver-types';
2
+ import type { ExpressionNode, NodeLocation } from '@player-ui/player';
3
+
4
+ /** Check if the vscode position overlaps with the expression location */
5
+ export function isInRange(position: Position, location: NodeLocation) {
6
+ return (
7
+ position.character >= location.start.character &&
8
+ position.character <= location.end.character
9
+ );
10
+ }
11
+
12
+ /** Find the closest marked token at the given position */
13
+ export function getTokenAtPosition(
14
+ node: ExpressionNode,
15
+ position: Position
16
+ ): ExpressionNode | undefined {
17
+ if (node.type === 'CallExpression') {
18
+ const anyArgs = node.args.find((arg) => {
19
+ return getTokenAtPosition(arg, position);
20
+ });
21
+
22
+ if (anyArgs) {
23
+ return anyArgs;
24
+ }
25
+
26
+ const asTarget = getTokenAtPosition(node.callTarget, position);
27
+ if (asTarget) {
28
+ return asTarget;
29
+ }
30
+ }
31
+
32
+ if (node.type === 'Assignment') {
33
+ const asTarget =
34
+ getTokenAtPosition(node.left, position) ??
35
+ getTokenAtPosition(node.right, position);
36
+ if (asTarget) {
37
+ return asTarget;
38
+ }
39
+ }
40
+
41
+ // Lastly check for yourself
42
+ if (node.location && isInRange(position, node.location)) {
43
+ return node;
44
+ }
45
+ }
@@ -0,0 +1,72 @@
1
+ import type * as ts from 'typescript/lib/tsserverlibrary';
2
+
3
+ export default class VirtualServiceHost implements ts.LanguageServiceHost {
4
+ private readonly files = new Map<string, string>();
5
+ private readonly typescript: typeof ts;
6
+ private readonly compilerOptions: ts.CompilerOptions;
7
+ private readonly workspacePath: string;
8
+
9
+ constructor(
10
+ typescript: typeof ts,
11
+ compilerOptions: ts.CompilerOptions,
12
+ workspacePath: string
13
+ ) {
14
+ this.typescript = typescript;
15
+ this.compilerOptions = compilerOptions;
16
+ this.workspacePath = workspacePath;
17
+ }
18
+
19
+ withFile<T>(fileName: string, content: string, callback: () => T): T {
20
+ this.files.set(fileName, content);
21
+ const result = callback();
22
+ this.files.delete(fileName);
23
+ return result;
24
+ }
25
+
26
+ getCompilationSettings() {
27
+ return this.compilerOptions;
28
+ }
29
+
30
+ getScriptFileNames() {
31
+ return Array.from(this.files.keys());
32
+ }
33
+
34
+ getScriptKind() {
35
+ return this.typescript.ScriptKind.TS;
36
+ }
37
+
38
+ getScriptVersion() {
39
+ return '0';
40
+ }
41
+
42
+ getScriptSnapshot(fileName: string) {
43
+ const fileText = this.readFile(fileName);
44
+ if (fileText) {
45
+ return this.typescript.ScriptSnapshot.fromString(fileText);
46
+ }
47
+ }
48
+
49
+ getCurrentDirectory() {
50
+ return this.workspacePath;
51
+ }
52
+
53
+ getDefaultLibFileName(options: ts.CompilerOptions) {
54
+ return this.typescript.getDefaultLibFilePath(options);
55
+ }
56
+
57
+ fileExists(path: string): boolean {
58
+ return path.includes('node_modules')
59
+ ? this.typescript.sys.fileExists(path)
60
+ : this.files.has(path);
61
+ }
62
+
63
+ readFile(path: string, encoding?: string | undefined): string | undefined {
64
+ return path.includes('node_modules')
65
+ ? this.typescript.sys.readFile(path, encoding)
66
+ : this.files.get(path);
67
+ }
68
+
69
+ useCaseSensitiveFileNames() {
70
+ return true;
71
+ }
72
+ }