@formspec/ts-plugin 0.1.0-alpha.21 → 0.1.0-alpha.22
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 +56 -0
- package/dist/__tests__/downstream-authoring-host.test.d.ts +2 -0
- package/dist/__tests__/downstream-authoring-host.test.d.ts.map +1 -0
- package/dist/__tests__/semantic-service.test.d.ts +2 -0
- package/dist/__tests__/semantic-service.test.d.ts.map +1 -0
- package/dist/index.cjs +465 -257
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +479 -269
- package/dist/index.js.map +1 -1
- package/dist/perf-utils.d.ts +3 -0
- package/dist/perf-utils.d.ts.map +1 -0
- package/dist/reference-host-example.d.ts +14 -0
- package/dist/reference-host-example.d.ts.map +1 -0
- package/dist/semantic-service.d.ts +116 -0
- package/dist/semantic-service.d.ts.map +1 -0
- package/dist/service.d.ts +38 -34
- package/dist/service.d.ts.map +1 -1
- package/dist/ts-plugin.d.ts +458 -0
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,25 +1,18 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import {
|
|
3
|
+
FORMSPEC_ANALYSIS_PROTOCOL_VERSION as FORMSPEC_ANALYSIS_PROTOCOL_VERSION4,
|
|
4
|
+
FORMSPEC_ANALYSIS_SCHEMA_VERSION as FORMSPEC_ANALYSIS_SCHEMA_VERSION2
|
|
5
|
+
} from "@formspec/analysis/protocol";
|
|
6
|
+
|
|
1
7
|
// src/service.ts
|
|
2
8
|
import fs from "fs/promises";
|
|
3
9
|
import net from "net";
|
|
4
10
|
import "typescript";
|
|
5
11
|
import {
|
|
6
|
-
FORMSPEC_ANALYSIS_PROTOCOL_VERSION as
|
|
7
|
-
computeFormSpecTextHash,
|
|
12
|
+
FORMSPEC_ANALYSIS_PROTOCOL_VERSION as FORMSPEC_ANALYSIS_PROTOCOL_VERSION3,
|
|
8
13
|
isFormSpecSemanticQuery
|
|
9
14
|
} from "@formspec/analysis/protocol";
|
|
10
|
-
import
|
|
11
|
-
buildFormSpecAnalysisFileSnapshot,
|
|
12
|
-
createFormSpecPerformanceRecorder,
|
|
13
|
-
findDeclarationForCommentOffset,
|
|
14
|
-
getCommentHoverInfoAtOffset,
|
|
15
|
-
getSemanticCommentCompletionContextAtOffset,
|
|
16
|
-
getFormSpecPerformanceNow,
|
|
17
|
-
getSubjectType,
|
|
18
|
-
optionalMeasure,
|
|
19
|
-
resolveDeclarationPlacement,
|
|
20
|
-
serializeCompletionContext,
|
|
21
|
-
serializeHoverInfo
|
|
22
|
-
} from "@formspec/analysis/internal";
|
|
15
|
+
import "@formspec/analysis/internal";
|
|
23
16
|
|
|
24
17
|
// src/workspace.ts
|
|
25
18
|
import os from "os";
|
|
@@ -79,112 +72,154 @@ function sanitizeScopeSegment(value) {
|
|
|
79
72
|
return sanitized.length > 0 ? sanitized : "formspec";
|
|
80
73
|
}
|
|
81
74
|
|
|
75
|
+
// src/semantic-service.ts
|
|
76
|
+
import "typescript";
|
|
77
|
+
import {
|
|
78
|
+
FORMSPEC_ANALYSIS_PROTOCOL_VERSION as FORMSPEC_ANALYSIS_PROTOCOL_VERSION2,
|
|
79
|
+
computeFormSpecTextHash
|
|
80
|
+
} from "@formspec/analysis/protocol";
|
|
81
|
+
import {
|
|
82
|
+
buildFormSpecAnalysisFileSnapshot,
|
|
83
|
+
createFormSpecPerformanceRecorder,
|
|
84
|
+
findDeclarationForCommentOffset,
|
|
85
|
+
getCommentHoverInfoAtOffset,
|
|
86
|
+
getSemanticCommentCompletionContextAtOffset,
|
|
87
|
+
getFormSpecPerformanceNow,
|
|
88
|
+
getSubjectType,
|
|
89
|
+
optionalMeasure,
|
|
90
|
+
resolveDeclarationPlacement,
|
|
91
|
+
serializeCompletionContext,
|
|
92
|
+
serializeHoverInfo
|
|
93
|
+
} from "@formspec/analysis/internal";
|
|
94
|
+
|
|
82
95
|
// src/constants.ts
|
|
83
96
|
var FORM_SPEC_PLUGIN_MAX_SOCKET_PAYLOAD_BYTES = 256 * 1024;
|
|
84
97
|
var FORM_SPEC_PLUGIN_SOCKET_IDLE_TIMEOUT_MS = 3e4;
|
|
85
98
|
var FORM_SPEC_PLUGIN_DEFAULT_PERFORMANCE_LOG_THRESHOLD_MS = 50;
|
|
86
99
|
var FORM_SPEC_PLUGIN_DEFAULT_SNAPSHOT_DEBOUNCE_MS = 250;
|
|
87
|
-
var FORM_SPEC_PLUGIN_PERFORMANCE_EVENT = {
|
|
88
|
-
handleQuery: "plugin.handleQuery"
|
|
89
|
-
};
|
|
90
100
|
|
|
91
|
-
// src/
|
|
92
|
-
|
|
101
|
+
// src/perf-utils.ts
|
|
102
|
+
function formatPerformanceEvent(event) {
|
|
103
|
+
const detailEntries = Object.entries(event.detail ?? {}).map(([key, value]) => `${key}=${String(value)}`).join(" ");
|
|
104
|
+
return `${event.durationMs.toFixed(1)}ms ${event.name}${detailEntries === "" ? "" : ` ${detailEntries}`}`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// src/semantic-service.ts
|
|
108
|
+
var STATS_ONLY_EVENT_NAMES = /* @__PURE__ */ new Set([
|
|
109
|
+
"analysis.syntheticCheckBatch.cacheHit",
|
|
110
|
+
"analysis.narrowSyntheticCheckBatch.cacheHit",
|
|
111
|
+
"analysis.syntheticCheckBatch.cacheMiss",
|
|
112
|
+
"analysis.narrowSyntheticCheckBatch.cacheMiss",
|
|
113
|
+
"analysis.syntheticCheckBatch.createProgram",
|
|
114
|
+
"analysis.narrowSyntheticCheckBatch.createProgram"
|
|
115
|
+
]);
|
|
116
|
+
var StatsOnlyPerformanceRecorder = class {
|
|
117
|
+
mutableEvents = [];
|
|
118
|
+
get events() {
|
|
119
|
+
return this.mutableEvents;
|
|
120
|
+
}
|
|
121
|
+
measure(name, detail, callback) {
|
|
122
|
+
const result = callback();
|
|
123
|
+
if (STATS_ONLY_EVENT_NAMES.has(name)) {
|
|
124
|
+
this.mutableEvents.push({
|
|
125
|
+
name,
|
|
126
|
+
durationMs: 0,
|
|
127
|
+
...detail === void 0 ? {} : { detail }
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
return result;
|
|
131
|
+
}
|
|
132
|
+
record(event) {
|
|
133
|
+
if (STATS_ONLY_EVENT_NAMES.has(event.name)) {
|
|
134
|
+
this.mutableEvents.push(event);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
var FormSpecSemanticService = class {
|
|
93
139
|
constructor(options) {
|
|
94
140
|
this.options = options;
|
|
95
|
-
this.runtimePaths = getFormSpecWorkspaceRuntimePaths(options.workspaceRoot);
|
|
96
|
-
this.manifest = createFormSpecAnalysisManifest(
|
|
97
|
-
options.workspaceRoot,
|
|
98
|
-
options.typescriptVersion,
|
|
99
|
-
Date.now()
|
|
100
|
-
);
|
|
101
141
|
}
|
|
102
|
-
manifest;
|
|
103
|
-
runtimePaths;
|
|
104
142
|
snapshotCache = /* @__PURE__ */ new Map();
|
|
105
143
|
refreshTimers = /* @__PURE__ */ new Map();
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
144
|
+
stats = {
|
|
145
|
+
queryTotals: {
|
|
146
|
+
completion: 0,
|
|
147
|
+
hover: 0,
|
|
148
|
+
diagnostics: 0,
|
|
149
|
+
fileSnapshot: 0
|
|
150
|
+
},
|
|
151
|
+
queryPathTotals: {
|
|
152
|
+
diagnostics: { cold: 0, warm: 0 },
|
|
153
|
+
fileSnapshot: { cold: 0, warm: 0 }
|
|
154
|
+
},
|
|
155
|
+
fileSnapshotCacheHits: 0,
|
|
156
|
+
fileSnapshotCacheMisses: 0,
|
|
157
|
+
syntheticBatchCacheHits: 0,
|
|
158
|
+
syntheticBatchCacheMisses: 0,
|
|
159
|
+
syntheticCompileCount: 0,
|
|
160
|
+
syntheticCompileApplications: 0
|
|
161
|
+
};
|
|
162
|
+
/** Resolves semantic completion context for a comment cursor position. */
|
|
163
|
+
getCompletionContext(filePath, offset) {
|
|
164
|
+
this.stats.queryTotals.completion += 1;
|
|
165
|
+
return this.runMeasured(
|
|
166
|
+
"semantic.getCompletionContext",
|
|
167
|
+
{ filePath, offset },
|
|
168
|
+
(performance2) => this.withCommentQueryContext(filePath, offset, performance2, (context) => ({
|
|
169
|
+
protocolVersion: FORMSPEC_ANALYSIS_PROTOCOL_VERSION2,
|
|
170
|
+
sourceHash: context.sourceHash,
|
|
171
|
+
context: serializeCompletionContext(
|
|
172
|
+
getSemanticCommentCompletionContextAtOffset(context.sourceFile.text, offset, {
|
|
173
|
+
checker: context.checker,
|
|
174
|
+
...context.placement === null ? {} : { placement: context.placement },
|
|
175
|
+
...context.subjectType === void 0 ? {} : { subjectType: context.subjectType }
|
|
176
|
+
})
|
|
177
|
+
)
|
|
178
|
+
}))
|
|
179
|
+
);
|
|
109
180
|
}
|
|
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
|
-
const newlineIndex = buffer.indexOf("\n");
|
|
141
|
-
if (newlineIndex < 0) {
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
const payload = buffer.slice(0, newlineIndex);
|
|
145
|
-
const remaining = buffer.slice(newlineIndex + 1);
|
|
146
|
-
if (remaining.trim().length > 0) {
|
|
147
|
-
this.options.logger?.info(
|
|
148
|
-
`[FormSpec] Ignoring extra semantic query payload data for ${this.runtimePaths.workspaceRoot}`
|
|
149
|
-
);
|
|
150
|
-
}
|
|
151
|
-
buffer = remaining;
|
|
152
|
-
this.respondToSocket(socket, payload);
|
|
153
|
-
});
|
|
154
|
-
});
|
|
155
|
-
await new Promise((resolve, reject) => {
|
|
156
|
-
const handleError = (error) => {
|
|
157
|
-
reject(error);
|
|
181
|
+
/** Resolves semantic hover payload for a comment cursor position. */
|
|
182
|
+
getHover(filePath, offset) {
|
|
183
|
+
this.stats.queryTotals.hover += 1;
|
|
184
|
+
return this.runMeasured(
|
|
185
|
+
"semantic.getHover",
|
|
186
|
+
{ filePath, offset },
|
|
187
|
+
(performance2) => this.withCommentQueryContext(filePath, offset, performance2, (context) => ({
|
|
188
|
+
protocolVersion: FORMSPEC_ANALYSIS_PROTOCOL_VERSION2,
|
|
189
|
+
sourceHash: context.sourceHash,
|
|
190
|
+
hover: serializeHoverInfo(
|
|
191
|
+
getCommentHoverInfoAtOffset(context.sourceFile.text, offset, {
|
|
192
|
+
checker: context.checker,
|
|
193
|
+
...context.placement === null ? {} : { placement: context.placement },
|
|
194
|
+
...context.subjectType === void 0 ? {} : { subjectType: context.subjectType }
|
|
195
|
+
})
|
|
196
|
+
)
|
|
197
|
+
}))
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
/** Returns canonical FormSpec diagnostics for a file in the current host program. */
|
|
201
|
+
getDiagnostics(filePath) {
|
|
202
|
+
this.stats.queryTotals.diagnostics += 1;
|
|
203
|
+
return this.runMeasured("semantic.getDiagnostics", { filePath }, (performance2) => {
|
|
204
|
+
const { snapshot, cacheState } = this.getFileSnapshotWithCacheState(filePath, performance2);
|
|
205
|
+
this.recordQueryPath("diagnostics", cacheState);
|
|
206
|
+
return {
|
|
207
|
+
protocolVersion: FORMSPEC_ANALYSIS_PROTOCOL_VERSION2,
|
|
208
|
+
sourceHash: snapshot.sourceHash,
|
|
209
|
+
diagnostics: snapshot.diagnostics
|
|
158
210
|
};
|
|
159
|
-
this.server?.once("error", handleError);
|
|
160
|
-
this.server?.listen(this.runtimePaths.endpoint.address, () => {
|
|
161
|
-
this.server?.off("error", handleError);
|
|
162
|
-
resolve();
|
|
163
|
-
});
|
|
164
211
|
});
|
|
165
|
-
await this.writeManifest();
|
|
166
212
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
if (server?.listening === true) {
|
|
176
|
-
await new Promise((resolve, reject) => {
|
|
177
|
-
server.close((error) => {
|
|
178
|
-
if (error === void 0) {
|
|
179
|
-
resolve();
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
reject(error);
|
|
183
|
-
});
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
await this.cleanupRuntimeArtifacts();
|
|
213
|
+
/** Returns the full serialized semantic snapshot for a file. */
|
|
214
|
+
getFileSnapshot(filePath) {
|
|
215
|
+
this.stats.queryTotals.fileSnapshot += 1;
|
|
216
|
+
return this.runMeasured("semantic.getFileSnapshot", { filePath }, (performance2) => {
|
|
217
|
+
const { snapshot, cacheState } = this.getFileSnapshotWithCacheState(filePath, performance2);
|
|
218
|
+
this.recordQueryPath("fileSnapshot", cacheState);
|
|
219
|
+
return snapshot;
|
|
220
|
+
});
|
|
187
221
|
}
|
|
222
|
+
/** Schedules a debounced background refresh for the file snapshot cache. */
|
|
188
223
|
scheduleSnapshotRefresh(filePath) {
|
|
189
224
|
const existing = this.refreshTimers.get(filePath);
|
|
190
225
|
if (existing !== void 0) {
|
|
@@ -192,7 +227,7 @@ var FormSpecPluginService = class {
|
|
|
192
227
|
}
|
|
193
228
|
const timer = setTimeout(() => {
|
|
194
229
|
try {
|
|
195
|
-
this.getFileSnapshot(filePath
|
|
230
|
+
this.getFileSnapshot(filePath);
|
|
196
231
|
} catch (error) {
|
|
197
232
|
this.options.logger?.info(
|
|
198
233
|
`[FormSpec] Failed to refresh semantic snapshot for ${filePath}: ${String(error)}`
|
|
@@ -200,76 +235,58 @@ var FormSpecPluginService = class {
|
|
|
200
235
|
}
|
|
201
236
|
this.refreshTimers.delete(filePath);
|
|
202
237
|
}, this.options.snapshotDebounceMs ?? FORM_SPEC_PLUGIN_DEFAULT_SNAPSHOT_DEBOUNCE_MS);
|
|
238
|
+
timer.unref();
|
|
203
239
|
this.refreshTimers.set(filePath, timer);
|
|
204
240
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
FORM_SPEC_PLUGIN_PERFORMANCE_EVENT.handleQuery,
|
|
210
|
-
{
|
|
211
|
-
kind: query.kind,
|
|
212
|
-
...query.kind === "health" ? {} : { filePath: query.filePath }
|
|
213
|
-
},
|
|
214
|
-
() => this.executeQuery(query, performance)
|
|
215
|
-
);
|
|
216
|
-
if (performance !== void 0) {
|
|
217
|
-
this.logPerformanceEvents(performance.events);
|
|
218
|
-
}
|
|
219
|
-
return response;
|
|
220
|
-
}
|
|
221
|
-
respondToSocket(socket, payload) {
|
|
222
|
-
try {
|
|
223
|
-
const query = JSON.parse(payload);
|
|
224
|
-
if (!isFormSpecSemanticQuery(query)) {
|
|
225
|
-
throw new Error("Invalid FormSpec semantic query payload");
|
|
226
|
-
}
|
|
227
|
-
const response = this.handleQuery(query);
|
|
228
|
-
socket.end(`${JSON.stringify(response)}
|
|
229
|
-
`);
|
|
230
|
-
} catch (error) {
|
|
231
|
-
socket.end(
|
|
232
|
-
`${JSON.stringify({
|
|
233
|
-
protocolVersion: FORMSPEC_ANALYSIS_PROTOCOL_VERSION2,
|
|
234
|
-
kind: "error",
|
|
235
|
-
error: error instanceof Error ? error.message : String(error)
|
|
236
|
-
})}
|
|
237
|
-
`
|
|
238
|
-
);
|
|
241
|
+
/** Clears pending timers and cached semantic snapshots. */
|
|
242
|
+
dispose() {
|
|
243
|
+
for (const timer of this.refreshTimers.values()) {
|
|
244
|
+
clearTimeout(timer);
|
|
239
245
|
}
|
|
246
|
+
this.refreshTimers.clear();
|
|
247
|
+
this.snapshotCache.clear();
|
|
240
248
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
249
|
+
/** Returns a copy of the current performance and cache counters. */
|
|
250
|
+
getStats() {
|
|
251
|
+
return {
|
|
252
|
+
queryTotals: { ...this.stats.queryTotals },
|
|
253
|
+
queryPathTotals: {
|
|
254
|
+
diagnostics: { ...this.stats.queryPathTotals.diagnostics },
|
|
255
|
+
fileSnapshot: { ...this.stats.queryPathTotals.fileSnapshot }
|
|
256
|
+
},
|
|
257
|
+
fileSnapshotCacheHits: this.stats.fileSnapshotCacheHits,
|
|
258
|
+
fileSnapshotCacheMisses: this.stats.fileSnapshotCacheMisses,
|
|
259
|
+
syntheticBatchCacheHits: this.stats.syntheticBatchCacheHits,
|
|
260
|
+
syntheticBatchCacheMisses: this.stats.syntheticBatchCacheMisses,
|
|
261
|
+
syntheticCompileCount: this.stats.syntheticCompileCount,
|
|
262
|
+
syntheticCompileApplications: this.stats.syntheticCompileApplications
|
|
263
|
+
};
|
|
246
264
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
265
|
+
runMeasured(name, detail, fn) {
|
|
266
|
+
const performance2 = this.options.enablePerformanceLogging === true ? createFormSpecPerformanceRecorder() : new StatsOnlyPerformanceRecorder();
|
|
267
|
+
const result = optionalMeasure(performance2, name, detail, () => fn(performance2));
|
|
268
|
+
this.updateStatsFromPerformanceEvents(performance2.events);
|
|
269
|
+
if (this.options.enablePerformanceLogging === true) {
|
|
270
|
+
this.logPerformanceEvents(name, performance2.events);
|
|
251
271
|
}
|
|
272
|
+
return result;
|
|
252
273
|
}
|
|
253
|
-
withCommentQueryContext(filePath, offset,
|
|
274
|
+
withCommentQueryContext(filePath, offset, performance2, handler) {
|
|
254
275
|
return optionalMeasure(
|
|
255
|
-
|
|
256
|
-
"
|
|
276
|
+
performance2,
|
|
277
|
+
"semantic.resolveCommentQueryContext",
|
|
257
278
|
{
|
|
258
279
|
filePath,
|
|
259
280
|
offset
|
|
260
281
|
},
|
|
261
282
|
() => {
|
|
262
|
-
const environment = this.getSourceEnvironment(filePath,
|
|
283
|
+
const environment = this.getSourceEnvironment(filePath, performance2);
|
|
263
284
|
if (environment === null) {
|
|
264
|
-
return
|
|
265
|
-
protocolVersion: FORMSPEC_ANALYSIS_PROTOCOL_VERSION2,
|
|
266
|
-
kind: "error",
|
|
267
|
-
error: `Unable to resolve TypeScript source file for ${filePath}`
|
|
268
|
-
};
|
|
285
|
+
return null;
|
|
269
286
|
}
|
|
270
287
|
const declaration = optionalMeasure(
|
|
271
|
-
|
|
272
|
-
"
|
|
288
|
+
performance2,
|
|
289
|
+
"semantic.findDeclarationForCommentOffset",
|
|
273
290
|
{
|
|
274
291
|
filePath,
|
|
275
292
|
offset
|
|
@@ -277,14 +294,14 @@ var FormSpecPluginService = class {
|
|
|
277
294
|
() => findDeclarationForCommentOffset(environment.sourceFile, offset)
|
|
278
295
|
);
|
|
279
296
|
const placement = declaration === null ? null : optionalMeasure(
|
|
280
|
-
|
|
281
|
-
"
|
|
297
|
+
performance2,
|
|
298
|
+
"semantic.resolveDeclarationPlacement",
|
|
282
299
|
void 0,
|
|
283
300
|
() => resolveDeclarationPlacement(declaration)
|
|
284
301
|
);
|
|
285
302
|
const subjectType = declaration === null ? void 0 : optionalMeasure(
|
|
286
|
-
|
|
287
|
-
"
|
|
303
|
+
performance2,
|
|
304
|
+
"semantic.getSubjectType",
|
|
288
305
|
void 0,
|
|
289
306
|
() => getSubjectType(declaration, environment.checker)
|
|
290
307
|
);
|
|
@@ -297,11 +314,12 @@ var FormSpecPluginService = class {
|
|
|
297
314
|
}
|
|
298
315
|
);
|
|
299
316
|
}
|
|
300
|
-
|
|
317
|
+
getFileSnapshotWithCacheState(filePath, performance2) {
|
|
301
318
|
const startedAt = getFormSpecPerformanceNow();
|
|
302
|
-
const environment = this.getSourceEnvironment(filePath,
|
|
319
|
+
const environment = this.getSourceEnvironment(filePath, performance2);
|
|
303
320
|
if (environment === null) {
|
|
304
|
-
|
|
321
|
+
this.stats.fileSnapshotCacheMisses += 1;
|
|
322
|
+
const snapshot2 = {
|
|
305
323
|
filePath,
|
|
306
324
|
sourceHash: "",
|
|
307
325
|
generatedAt: this.getNow().toISOString(),
|
|
@@ -309,131 +327,83 @@ var FormSpecPluginService = class {
|
|
|
309
327
|
diagnostics: [
|
|
310
328
|
{
|
|
311
329
|
code: "MISSING_SOURCE_FILE",
|
|
330
|
+
category: "infrastructure",
|
|
312
331
|
message: `Unable to resolve TypeScript source file for ${filePath}`,
|
|
313
332
|
range: { start: 0, end: 0 },
|
|
314
|
-
severity: "warning"
|
|
333
|
+
severity: "warning",
|
|
334
|
+
relatedLocations: [],
|
|
335
|
+
data: {
|
|
336
|
+
filePath
|
|
337
|
+
}
|
|
315
338
|
}
|
|
316
339
|
]
|
|
317
340
|
};
|
|
318
|
-
|
|
319
|
-
name: "
|
|
341
|
+
performance2.record({
|
|
342
|
+
name: "semantic.getFileSnapshot.result",
|
|
320
343
|
durationMs: getFormSpecPerformanceNow() - startedAt,
|
|
321
344
|
detail: {
|
|
322
345
|
filePath,
|
|
323
346
|
cache: "missing-source"
|
|
324
347
|
}
|
|
325
348
|
});
|
|
326
|
-
return
|
|
349
|
+
return {
|
|
350
|
+
snapshot: snapshot2,
|
|
351
|
+
cacheState: "missing-source"
|
|
352
|
+
};
|
|
327
353
|
}
|
|
328
354
|
const cached = this.snapshotCache.get(filePath);
|
|
329
355
|
if (cached?.sourceHash === environment.sourceHash) {
|
|
330
|
-
|
|
331
|
-
|
|
356
|
+
this.stats.fileSnapshotCacheHits += 1;
|
|
357
|
+
performance2.record({
|
|
358
|
+
name: "semantic.getFileSnapshot.result",
|
|
332
359
|
durationMs: getFormSpecPerformanceNow() - startedAt,
|
|
333
360
|
detail: {
|
|
334
361
|
filePath,
|
|
335
362
|
cache: "hit"
|
|
336
363
|
}
|
|
337
364
|
});
|
|
338
|
-
return
|
|
365
|
+
return {
|
|
366
|
+
snapshot: cached.snapshot,
|
|
367
|
+
cacheState: "hit"
|
|
368
|
+
};
|
|
339
369
|
}
|
|
370
|
+
this.stats.fileSnapshotCacheMisses += 1;
|
|
340
371
|
const snapshot = buildFormSpecAnalysisFileSnapshot(environment.sourceFile, {
|
|
341
372
|
checker: environment.checker,
|
|
342
373
|
now: () => this.getNow(),
|
|
343
|
-
|
|
374
|
+
performance: performance2
|
|
344
375
|
});
|
|
345
376
|
this.snapshotCache.set(filePath, {
|
|
346
377
|
sourceHash: environment.sourceHash,
|
|
347
378
|
snapshot
|
|
348
379
|
});
|
|
349
|
-
|
|
350
|
-
name: "
|
|
380
|
+
performance2.record({
|
|
381
|
+
name: "semantic.getFileSnapshot.result",
|
|
351
382
|
durationMs: getFormSpecPerformanceNow() - startedAt,
|
|
352
383
|
detail: {
|
|
353
384
|
filePath,
|
|
354
385
|
cache: "miss"
|
|
355
386
|
}
|
|
356
387
|
});
|
|
357
|
-
return
|
|
388
|
+
return {
|
|
389
|
+
snapshot,
|
|
390
|
+
cacheState: "miss"
|
|
391
|
+
};
|
|
358
392
|
}
|
|
359
393
|
getNow() {
|
|
360
394
|
return this.options.now?.() ?? /* @__PURE__ */ new Date();
|
|
361
395
|
}
|
|
362
|
-
|
|
363
|
-
switch (query.kind) {
|
|
364
|
-
case "health":
|
|
365
|
-
return {
|
|
366
|
-
protocolVersion: FORMSPEC_ANALYSIS_PROTOCOL_VERSION2,
|
|
367
|
-
kind: "health",
|
|
368
|
-
manifest: this.manifest
|
|
369
|
-
};
|
|
370
|
-
case "completion":
|
|
371
|
-
return this.withCommentQueryContext(
|
|
372
|
-
query.filePath,
|
|
373
|
-
query.offset,
|
|
374
|
-
(context) => ({
|
|
375
|
-
protocolVersion: FORMSPEC_ANALYSIS_PROTOCOL_VERSION2,
|
|
376
|
-
kind: "completion",
|
|
377
|
-
sourceHash: context.sourceHash,
|
|
378
|
-
context: serializeCompletionContext(
|
|
379
|
-
getSemanticCommentCompletionContextAtOffset(context.sourceFile.text, query.offset, {
|
|
380
|
-
checker: context.checker,
|
|
381
|
-
...context.placement === null ? {} : { placement: context.placement },
|
|
382
|
-
...context.subjectType === void 0 ? {} : { subjectType: context.subjectType }
|
|
383
|
-
})
|
|
384
|
-
)
|
|
385
|
-
}),
|
|
386
|
-
performance
|
|
387
|
-
);
|
|
388
|
-
case "hover":
|
|
389
|
-
return this.withCommentQueryContext(
|
|
390
|
-
query.filePath,
|
|
391
|
-
query.offset,
|
|
392
|
-
(context) => ({
|
|
393
|
-
protocolVersion: FORMSPEC_ANALYSIS_PROTOCOL_VERSION2,
|
|
394
|
-
kind: "hover",
|
|
395
|
-
sourceHash: context.sourceHash,
|
|
396
|
-
hover: serializeHoverInfo(
|
|
397
|
-
getCommentHoverInfoAtOffset(context.sourceFile.text, query.offset, {
|
|
398
|
-
checker: context.checker,
|
|
399
|
-
...context.placement === null ? {} : { placement: context.placement },
|
|
400
|
-
...context.subjectType === void 0 ? {} : { subjectType: context.subjectType }
|
|
401
|
-
})
|
|
402
|
-
)
|
|
403
|
-
}),
|
|
404
|
-
performance
|
|
405
|
-
);
|
|
406
|
-
case "diagnostics": {
|
|
407
|
-
const snapshot = this.getFileSnapshot(query.filePath, performance);
|
|
408
|
-
return {
|
|
409
|
-
protocolVersion: FORMSPEC_ANALYSIS_PROTOCOL_VERSION2,
|
|
410
|
-
kind: "diagnostics",
|
|
411
|
-
sourceHash: snapshot.sourceHash,
|
|
412
|
-
diagnostics: snapshot.diagnostics
|
|
413
|
-
};
|
|
414
|
-
}
|
|
415
|
-
case "file-snapshot":
|
|
416
|
-
return {
|
|
417
|
-
protocolVersion: FORMSPEC_ANALYSIS_PROTOCOL_VERSION2,
|
|
418
|
-
kind: "file-snapshot",
|
|
419
|
-
snapshot: this.getFileSnapshot(query.filePath, performance)
|
|
420
|
-
};
|
|
421
|
-
default: {
|
|
422
|
-
throw new Error(`Unhandled semantic query: ${JSON.stringify(query)}`);
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
getSourceEnvironment(filePath, performance) {
|
|
396
|
+
getSourceEnvironment(filePath, performance2) {
|
|
427
397
|
return optionalMeasure(
|
|
428
|
-
|
|
429
|
-
"
|
|
398
|
+
performance2,
|
|
399
|
+
"semantic.getSourceEnvironment",
|
|
430
400
|
{
|
|
431
401
|
filePath
|
|
432
402
|
},
|
|
433
403
|
() => {
|
|
434
404
|
const program = optionalMeasure(
|
|
435
|
-
|
|
436
|
-
"
|
|
405
|
+
performance2,
|
|
406
|
+
"semantic.sourceEnvironment.getProgram",
|
|
437
407
|
void 0,
|
|
438
408
|
() => this.options.getProgram()
|
|
439
409
|
);
|
|
@@ -441,8 +411,8 @@ var FormSpecPluginService = class {
|
|
|
441
411
|
return null;
|
|
442
412
|
}
|
|
443
413
|
const sourceFile = optionalMeasure(
|
|
444
|
-
|
|
445
|
-
"
|
|
414
|
+
performance2,
|
|
415
|
+
"semantic.sourceEnvironment.getSourceFile",
|
|
446
416
|
void 0,
|
|
447
417
|
() => program.getSourceFile(filePath)
|
|
448
418
|
);
|
|
@@ -450,14 +420,14 @@ var FormSpecPluginService = class {
|
|
|
450
420
|
return null;
|
|
451
421
|
}
|
|
452
422
|
const checker = optionalMeasure(
|
|
453
|
-
|
|
454
|
-
"
|
|
423
|
+
performance2,
|
|
424
|
+
"semantic.sourceEnvironment.getTypeChecker",
|
|
455
425
|
void 0,
|
|
456
426
|
() => program.getTypeChecker()
|
|
457
427
|
);
|
|
458
428
|
const sourceHash = optionalMeasure(
|
|
459
|
-
|
|
460
|
-
"
|
|
429
|
+
performance2,
|
|
430
|
+
"semantic.sourceEnvironment.computeTextHash",
|
|
461
431
|
void 0,
|
|
462
432
|
() => computeFormSpecTextHash(sourceFile.text)
|
|
463
433
|
);
|
|
@@ -469,19 +439,38 @@ var FormSpecPluginService = class {
|
|
|
469
439
|
}
|
|
470
440
|
);
|
|
471
441
|
}
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
442
|
+
recordQueryPath(kind, cacheState) {
|
|
443
|
+
if (cacheState === "hit") {
|
|
444
|
+
this.stats.queryPathTotals[kind].warm += 1;
|
|
475
445
|
return;
|
|
476
446
|
}
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
447
|
+
this.stats.queryPathTotals[kind].cold += 1;
|
|
448
|
+
}
|
|
449
|
+
updateStatsFromPerformanceEvents(events) {
|
|
450
|
+
for (const event of events) {
|
|
451
|
+
if (event.name === "analysis.syntheticCheckBatch.cacheHit" || event.name === "analysis.narrowSyntheticCheckBatch.cacheHit") {
|
|
452
|
+
this.stats.syntheticBatchCacheHits += 1;
|
|
453
|
+
continue;
|
|
483
454
|
}
|
|
455
|
+
if (event.name === "analysis.syntheticCheckBatch.cacheMiss" || event.name === "analysis.narrowSyntheticCheckBatch.cacheMiss") {
|
|
456
|
+
this.stats.syntheticBatchCacheMisses += 1;
|
|
457
|
+
const applicationCount = event.detail?.["applicationCount"];
|
|
458
|
+
if (typeof applicationCount === "number") {
|
|
459
|
+
this.stats.syntheticCompileApplications += applicationCount;
|
|
460
|
+
}
|
|
461
|
+
continue;
|
|
462
|
+
}
|
|
463
|
+
if (event.name === "analysis.syntheticCheckBatch.createProgram" || event.name === "analysis.narrowSyntheticCheckBatch.createProgram") {
|
|
464
|
+
this.stats.syntheticCompileCount += 1;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
logPerformanceEvents(rootEventName, events) {
|
|
469
|
+
const logger = this.options.logger;
|
|
470
|
+
if (logger === void 0 || events.length === 0) {
|
|
471
|
+
return;
|
|
484
472
|
}
|
|
473
|
+
const rootEvent = [...events].reverse().find((event) => event.name === rootEventName);
|
|
485
474
|
if (rootEvent === void 0) {
|
|
486
475
|
return;
|
|
487
476
|
}
|
|
@@ -489,18 +478,234 @@ var FormSpecPluginService = class {
|
|
|
489
478
|
if (rootEvent.durationMs < thresholdMs) {
|
|
490
479
|
return;
|
|
491
480
|
}
|
|
492
|
-
const sortedHotspots = [...events].filter((event) => event.name !==
|
|
481
|
+
const sortedHotspots = [...events].filter((event) => event.name !== rootEventName).sort((left, right) => right.durationMs - left.durationMs).slice(0, 8);
|
|
493
482
|
const lines = [
|
|
494
|
-
`[FormSpec][perf] ${
|
|
483
|
+
`[FormSpec][perf] ${formatPerformanceEvent(rootEvent)}`,
|
|
495
484
|
...sortedHotspots.map((event) => ` ${formatPerformanceEvent(event)}`)
|
|
496
485
|
];
|
|
497
486
|
logger.info(lines.join("\n"));
|
|
498
487
|
}
|
|
499
488
|
};
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
489
|
+
|
|
490
|
+
// src/service.ts
|
|
491
|
+
var FormSpecPluginService = class {
|
|
492
|
+
constructor(options) {
|
|
493
|
+
this.options = options;
|
|
494
|
+
this.semanticService = new FormSpecSemanticService(options);
|
|
495
|
+
this.runtimePaths = getFormSpecWorkspaceRuntimePaths(options.workspaceRoot);
|
|
496
|
+
this.manifest = createFormSpecAnalysisManifest(
|
|
497
|
+
options.workspaceRoot,
|
|
498
|
+
options.typescriptVersion,
|
|
499
|
+
Date.now()
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
manifest;
|
|
503
|
+
runtimePaths;
|
|
504
|
+
semanticService;
|
|
505
|
+
server = null;
|
|
506
|
+
getManifest() {
|
|
507
|
+
return this.manifest;
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Returns the underlying semantic service used by this reference wrapper.
|
|
511
|
+
*
|
|
512
|
+
* @public
|
|
513
|
+
*/
|
|
514
|
+
getSemanticService() {
|
|
515
|
+
return this.semanticService;
|
|
516
|
+
}
|
|
517
|
+
async start() {
|
|
518
|
+
if (this.server !== null) {
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
await fs.mkdir(this.runtimePaths.runtimeDirectory, { recursive: true });
|
|
522
|
+
if (this.runtimePaths.endpoint.kind === "unix-socket") {
|
|
523
|
+
await fs.rm(this.runtimePaths.endpoint.address, { force: true });
|
|
524
|
+
}
|
|
525
|
+
this.server = net.createServer((socket) => {
|
|
526
|
+
let buffer = "";
|
|
527
|
+
socket.setEncoding("utf8");
|
|
528
|
+
socket.setTimeout(FORM_SPEC_PLUGIN_SOCKET_IDLE_TIMEOUT_MS, () => {
|
|
529
|
+
this.options.logger?.info(
|
|
530
|
+
`[FormSpec] Closing idle semantic query socket for ${this.runtimePaths.workspaceRoot}`
|
|
531
|
+
);
|
|
532
|
+
socket.destroy();
|
|
533
|
+
});
|
|
534
|
+
socket.on("data", (chunk) => {
|
|
535
|
+
buffer += String(chunk);
|
|
536
|
+
if (buffer.length > FORM_SPEC_PLUGIN_MAX_SOCKET_PAYLOAD_BYTES) {
|
|
537
|
+
socket.end(
|
|
538
|
+
`${JSON.stringify({
|
|
539
|
+
protocolVersion: FORMSPEC_ANALYSIS_PROTOCOL_VERSION3,
|
|
540
|
+
kind: "error",
|
|
541
|
+
error: `FormSpec semantic query exceeded ${String(FORM_SPEC_PLUGIN_MAX_SOCKET_PAYLOAD_BYTES)} bytes`
|
|
542
|
+
})}
|
|
543
|
+
`
|
|
544
|
+
);
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
const newlineIndex = buffer.indexOf("\n");
|
|
548
|
+
if (newlineIndex < 0) {
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
const payload = buffer.slice(0, newlineIndex);
|
|
552
|
+
const remaining = buffer.slice(newlineIndex + 1);
|
|
553
|
+
if (remaining.trim().length > 0) {
|
|
554
|
+
this.options.logger?.info(
|
|
555
|
+
`[FormSpec] Ignoring extra semantic query payload data for ${this.runtimePaths.workspaceRoot}`
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
buffer = remaining;
|
|
559
|
+
this.respondToSocket(socket, payload);
|
|
560
|
+
});
|
|
561
|
+
});
|
|
562
|
+
await new Promise((resolve, reject) => {
|
|
563
|
+
const handleError = (error) => {
|
|
564
|
+
reject(error);
|
|
565
|
+
};
|
|
566
|
+
this.server?.once("error", handleError);
|
|
567
|
+
this.server?.listen(this.runtimePaths.endpoint.address, () => {
|
|
568
|
+
this.server?.off("error", handleError);
|
|
569
|
+
resolve();
|
|
570
|
+
});
|
|
571
|
+
});
|
|
572
|
+
await this.writeManifest();
|
|
573
|
+
}
|
|
574
|
+
async stop() {
|
|
575
|
+
this.semanticService.dispose();
|
|
576
|
+
const server = this.server;
|
|
577
|
+
this.server = null;
|
|
578
|
+
if (server?.listening === true) {
|
|
579
|
+
await new Promise((resolve, reject) => {
|
|
580
|
+
server.close((error) => {
|
|
581
|
+
if (error === void 0) {
|
|
582
|
+
resolve();
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
reject(error);
|
|
586
|
+
});
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
await this.cleanupRuntimeArtifacts();
|
|
590
|
+
}
|
|
591
|
+
scheduleSnapshotRefresh(filePath) {
|
|
592
|
+
this.semanticService.scheduleSnapshotRefresh(filePath);
|
|
593
|
+
}
|
|
594
|
+
handleQuery(query) {
|
|
595
|
+
if (this.options.enablePerformanceLogging === true) {
|
|
596
|
+
const startedAt = performance.now();
|
|
597
|
+
const response = this.executeQuery(query);
|
|
598
|
+
this.logQueryDuration(query, performance.now() - startedAt);
|
|
599
|
+
return response;
|
|
600
|
+
}
|
|
601
|
+
return this.executeQuery(query);
|
|
602
|
+
}
|
|
603
|
+
executeQuery(query) {
|
|
604
|
+
switch (query.kind) {
|
|
605
|
+
case "health":
|
|
606
|
+
return {
|
|
607
|
+
protocolVersion: FORMSPEC_ANALYSIS_PROTOCOL_VERSION3,
|
|
608
|
+
kind: "health",
|
|
609
|
+
manifest: this.manifest
|
|
610
|
+
};
|
|
611
|
+
case "completion": {
|
|
612
|
+
const result = this.semanticService.getCompletionContext(query.filePath, query.offset);
|
|
613
|
+
if (result === null) {
|
|
614
|
+
return {
|
|
615
|
+
protocolVersion: FORMSPEC_ANALYSIS_PROTOCOL_VERSION3,
|
|
616
|
+
kind: "error",
|
|
617
|
+
error: `Unable to resolve TypeScript source file for ${query.filePath}`
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
return {
|
|
621
|
+
...result,
|
|
622
|
+
kind: "completion"
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
case "hover": {
|
|
626
|
+
const result = this.semanticService.getHover(query.filePath, query.offset);
|
|
627
|
+
if (result === null) {
|
|
628
|
+
return {
|
|
629
|
+
protocolVersion: FORMSPEC_ANALYSIS_PROTOCOL_VERSION3,
|
|
630
|
+
kind: "error",
|
|
631
|
+
error: `Unable to resolve TypeScript source file for ${query.filePath}`
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
return {
|
|
635
|
+
...result,
|
|
636
|
+
kind: "hover"
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
case "diagnostics": {
|
|
640
|
+
const result = this.semanticService.getDiagnostics(query.filePath);
|
|
641
|
+
return {
|
|
642
|
+
...result,
|
|
643
|
+
kind: "diagnostics"
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
case "file-snapshot":
|
|
647
|
+
return {
|
|
648
|
+
protocolVersion: FORMSPEC_ANALYSIS_PROTOCOL_VERSION3,
|
|
649
|
+
kind: "file-snapshot",
|
|
650
|
+
snapshot: this.semanticService.getFileSnapshot(query.filePath)
|
|
651
|
+
};
|
|
652
|
+
default: {
|
|
653
|
+
throw new Error(`Unhandled semantic query: ${JSON.stringify(query)}`);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
respondToSocket(socket, payload) {
|
|
658
|
+
try {
|
|
659
|
+
const query = JSON.parse(payload);
|
|
660
|
+
if (!isFormSpecSemanticQuery(query)) {
|
|
661
|
+
throw new Error("Invalid FormSpec semantic query payload");
|
|
662
|
+
}
|
|
663
|
+
const response = this.handleQuery(query);
|
|
664
|
+
socket.end(`${JSON.stringify(response)}
|
|
665
|
+
`);
|
|
666
|
+
} catch (error) {
|
|
667
|
+
socket.end(
|
|
668
|
+
`${JSON.stringify({
|
|
669
|
+
protocolVersion: FORMSPEC_ANALYSIS_PROTOCOL_VERSION3,
|
|
670
|
+
kind: "error",
|
|
671
|
+
error: error instanceof Error ? error.message : String(error)
|
|
672
|
+
})}
|
|
673
|
+
`
|
|
674
|
+
);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
async writeManifest() {
|
|
678
|
+
const tempManifestPath = `${this.runtimePaths.manifestPath}.tmp`;
|
|
679
|
+
await fs.writeFile(tempManifestPath, `${JSON.stringify(this.manifest, null, 2)}
|
|
680
|
+
`, "utf8");
|
|
681
|
+
await fs.rename(tempManifestPath, this.runtimePaths.manifestPath);
|
|
682
|
+
}
|
|
683
|
+
async cleanupRuntimeArtifacts() {
|
|
684
|
+
await fs.rm(this.runtimePaths.manifestPath, { force: true });
|
|
685
|
+
if (this.runtimePaths.endpoint.kind === "unix-socket") {
|
|
686
|
+
await fs.rm(this.runtimePaths.endpoint.address, { force: true });
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
logQueryDuration(query, durationMs) {
|
|
690
|
+
const logger = this.options.logger;
|
|
691
|
+
if (logger === void 0) {
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
const thresholdMs = this.options.performanceLogThresholdMs ?? FORM_SPEC_PLUGIN_DEFAULT_PERFORMANCE_LOG_THRESHOLD_MS;
|
|
695
|
+
if (durationMs < thresholdMs) {
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
const event = {
|
|
699
|
+
name: "plugin.handleQuery",
|
|
700
|
+
durationMs,
|
|
701
|
+
detail: {
|
|
702
|
+
kind: query.kind,
|
|
703
|
+
...query.kind === "health" ? {} : { filePath: query.filePath }
|
|
704
|
+
}
|
|
705
|
+
};
|
|
706
|
+
logger.info(`[FormSpec][perf] ${formatPerformanceEvent(event)}`);
|
|
707
|
+
}
|
|
708
|
+
};
|
|
504
709
|
function createLanguageServiceProxy(languageService, semanticService) {
|
|
505
710
|
const wrapWithSnapshotRefresh = (fn) => {
|
|
506
711
|
return (fileName, ...args) => {
|
|
@@ -609,11 +814,16 @@ function init(modules) {
|
|
|
609
814
|
return {
|
|
610
815
|
create(info) {
|
|
611
816
|
const service = getOrCreateService(info, typescriptVersion);
|
|
612
|
-
return createLanguageServiceProxy(info.languageService, service);
|
|
817
|
+
return createLanguageServiceProxy(info.languageService, service.getSemanticService());
|
|
613
818
|
}
|
|
614
819
|
};
|
|
615
820
|
}
|
|
616
821
|
export {
|
|
822
|
+
FORMSPEC_ANALYSIS_PROTOCOL_VERSION4 as FORMSPEC_ANALYSIS_PROTOCOL_VERSION,
|
|
823
|
+
FORMSPEC_ANALYSIS_SCHEMA_VERSION2 as FORMSPEC_ANALYSIS_SCHEMA_VERSION,
|
|
824
|
+
FormSpecPluginService,
|
|
825
|
+
FormSpecSemanticService,
|
|
826
|
+
createLanguageServiceProxy,
|
|
617
827
|
init
|
|
618
828
|
};
|
|
619
829
|
//# sourceMappingURL=index.js.map
|