-
Notifications
You must be signed in to change notification settings - Fork 14
Expand file tree
/
Copy pathextension.ts
More file actions
279 lines (230 loc) · 13 KB
/
Copy pathextension.ts
File metadata and controls
279 lines (230 loc) · 13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
import * as vscode from 'vscode';
import { config, Configuration } from "./configuration";
import { BehaveTestData, Scenario, TestData, TestFile } from './parsers/testFile';
import {
getContentFromFilesystem,
getUrisOfWkspFoldersWithFeatures, getWorkspaceSettingsForFile, isFeatureFile,
isStepsFile, logExtensionVersion, cleanExtensionTempDirectory, urisMatch
} from './common';
import { StepFileStep } from './parsers/stepsParser';
import { gotoStepHandler } from './handlers/gotoStepHandler';
import { findStepReferencesHandler, nextStepReferenceHandler as nextStepReferenceHandler, prevStepReferenceHandler, treeView } from './handlers/findStepReferencesHandler';
import { FileParser } from './parsers/fileParser';
import { testRunHandler } from './runners/testRunHandler';
import { TestWorkspaceConfigWithWkspUri } from './_integrationTests/suite-shared/testWorkspaceConfig';
import { diagLog } from './logger';
import { performance } from 'perf_hooks';
import { StepMapping, getStepFileStepForFeatureFileStep, getStepMappingsForStepsFileFunction } from './parsers/stepMappings';
import { autoCompleteProvider } from './handlers/autoCompleteProvider';
import { formatFeatureProvider } from './handlers/formatFeatureProvider';
import { SemHighlightProvider, semLegend } from './handlers/semHighlightProvider';
import { DefinitionProvider } from './handlers/definitionProvider';
import { startWatchingWorkspace } from './watchers/workspaceWatcher';
import { JunitWatcher } from './watchers/junitWatcher';
const testData = new WeakMap<vscode.TestItem, BehaveTestData>();
const wkspWatchers = new Map<vscode.Uri, vscode.FileSystemWatcher[]>();
export const parser = new FileParser();
export interface QueueItem { test: vscode.TestItem; scenario: Scenario; }
export type TestSupport = {
runHandler: (debug: boolean, request: vscode.TestRunRequest) => Promise<QueueItem[] | undefined>,
config: Configuration,
ctrl: vscode.TestController,
parser: FileParser,
getStepMappingsForStepsFileFunction: (stepsFileUri: vscode.Uri, lineNo: number) => StepMapping[],
getStepFileStepForFeatureFileStep: (featureFileUri: vscode.Uri, line: number) => StepFileStep | undefined,
testData: TestData,
configurationChangedHandler: (event?: vscode.ConfigurationChangeEvent, testCfg?: TestWorkspaceConfigWithWkspUri, forceRefresh?: boolean) => Promise<void>
};
// construction function called on extension activation OR the first time a new/unrecognised workspace gets added.
// - call anything that needs to be initialised/kicked off async on startup, and
// - set up all relevant event handlers/hooks/subscriptions to the vscode api
// NOTE - THIS MUST RETURN FAST: AVOID using "await" here unless absolutely necessary (except inside handlers)
// this function should only contain initialisation, registering event handlers, and unawaited async calls
export async function activate(context: vscode.ExtensionContext): Promise<TestSupport | undefined> {
try {
const start = performance.now();
diagLog("activate called, node pid:" + process.pid);
config.logger.syncChannelsToWorkspaceFolders();
logExtensionVersion(context);
const ctrl = vscode.tests.createTestController(`behave-vsc.TestController`, 'Feature Tests');
parser.clearTestItemsAndParseFilesForAllWorkspaces(testData, ctrl, "activate", true);
const cleanExtensionTempDirectoryCancelSource = new vscode.CancellationTokenSource();
cleanExtensionTempDirectory(cleanExtensionTempDirectoryCancelSource.token);
for (const wkspUri of getUrisOfWkspFoldersWithFeatures()) {
const watchers = startWatchingWorkspace(wkspUri, ctrl, testData, parser);
wkspWatchers.set(wkspUri, watchers);
watchers.forEach(w => context.subscriptions.push(w));
}
const junitWatcher = new JunitWatcher();
junitWatcher.startWatchingJunitFolder();
// any function contained in a context.subscriptions.push() will execute immediately,
// as well as registering the returned disposable object for a dispose() call on extension deactivation
// i.e. startWatchingWorkspace will execute immediately, as will registerCommand, but gotoStepHandler will not (as it is a parameter
// to a register command, which returns a disposable so our custom command is deregistered when the extension is deactivated).
// to test any custom dispose() methods (which must be synchronous), just start and then close the extension host environment.
context.subscriptions.push(
ctrl,
treeView,
config,
cleanExtensionTempDirectoryCancelSource,
junitWatcher,
vscode.commands.registerTextEditorCommand(`behave-vsc.gotoStep`, gotoStepHandler),
vscode.commands.registerTextEditorCommand(`behave-vsc.findStepReferences`, findStepReferencesHandler),
vscode.commands.registerCommand(`behave-vsc.stepReferences.prev`, prevStepReferenceHandler),
vscode.commands.registerCommand(`behave-vsc.stepReferences.next`, nextStepReferenceHandler),
vscode.languages.registerCompletionItemProvider('gherkin', autoCompleteProvider, ...[" "]),
vscode.languages.registerDocumentRangeFormattingEditProvider('gherkin', formatFeatureProvider),
vscode.languages.registerDocumentSemanticTokensProvider({ language: 'gherkin' }, new SemHighlightProvider(), semLegend),
vscode.languages.registerDefinitionProvider({ language: 'gherkin' }, new DefinitionProvider())
);
const runHandler = testRunHandler(testData, ctrl, parser, junitWatcher, cleanExtensionTempDirectoryCancelSource);
ctrl.createRunProfile('Run Tests', vscode.TestRunProfileKind.Run,
async (request: vscode.TestRunRequest) => {
await runHandler(false, request);
}
, true);
ctrl.createRunProfile('Debug Tests', vscode.TestRunProfileKind.Debug,
async (request: vscode.TestRunRequest) => {
await runHandler(true, request);
}
, true);
ctrl.resolveHandler = async (item: vscode.TestItem | undefined) => {
let wkspSettings;
try {
if (!item || !item.uri || item.uri?.scheme !== 'file')
return;
const data = testData.get(item);
if (!(data instanceof TestFile))
return;
wkspSettings = getWorkspaceSettingsForFile(item.uri);
const content = await getContentFromFilesystem(item.uri);
await data.createScenarioTestItemsFromFeatureFileContent(wkspSettings, content, testData, ctrl, item, "resolveHandler");
}
catch (e: unknown) {
// entry point function (handler) - show error
const wkspUri = wkspSettings ? wkspSettings.uri : undefined;
config.logger.showError(e, wkspUri);
}
};
ctrl.refreshHandler = async (cancelToken: vscode.CancellationToken) => {
try {
await parser.clearTestItemsAndParseFilesForAllWorkspaces(testData, ctrl, "refreshHandler", false, cancelToken);
}
catch (e: unknown) {
// entry point function (handler) - show error
config.logger.showError(e, undefined);
}
};
// called when a user renames, adds or removes a workspace folder.
// NOTE: the first time a new not-previously recognised workspace gets added a new node host
// process will start, this host process will terminate, and activate() will be called shortly after
// eslint-disable-next-line @typescript-eslint/no-unused-vars
context.subscriptions.push(vscode.workspace.onDidChangeWorkspaceFolders(async (event) => {
try {
await configurationChangedHandler(undefined, undefined, true);
}
catch (e: unknown) {
// entry point function (handler) - show error
config.logger.showError(e, undefined);
}
}));
// called when a user edits a file.
// we want to reparse on edit (not just on disk changes) because:
// a. the user may run a file they just edited without saving,
// b. the semantic highlighting while typing requires the stepmappings to be up to date as the user types,
// c. instant test tree updates is a nice bonus for user experience
// d. to keep stepmappings in sync in case user clicks go to step def/ref before file save
context.subscriptions.push(vscode.workspace.onDidChangeTextDocument(async (event) => {
try {
const uri = event.document.uri;
if (!isFeatureFile(uri) && !isStepsFile(uri))
return;
const wkspSettings = getWorkspaceSettingsForFile(uri);
parser.reparseFile(uri, event.document.getText(), wkspSettings, testData, ctrl);
}
catch (e: unknown) {
// entry point function (handler) - show error
config.logger.showError(e, undefined);
}
}));
// called by onDidChangeConfiguration when there is a settings.json/*.vscode-workspace change
// and onDidChangeWorkspaceFolders (also called by integration tests with a testCfg).
// NOTE: in some circumstances this function can be called twice in quick succession when a multi-root workspace folder is added/removed/renamed
// (i.e. once by onDidChangeWorkspaceFolders and once by onDidChangeConfiguration), but parser methods will self-cancel as needed
const configurationChangedHandler = async (event?: vscode.ConfigurationChangeEvent, testCfg?: TestWorkspaceConfigWithWkspUri,
forceFullRefresh?: boolean) => {
// for integration test runAllTestsAndAssertTheResults,
// only reload config on request (i.e. when testCfg supplied)
if (config.integrationTestRun && !testCfg)
return;
try {
// note - affectsConfiguration(ext,uri) i.e. with a scope (uri) param is smart re. default resource values, but we don't want
// that behaviour because we want to distinguish between some properties being set vs being absent from
// settings.json (via inspect not get), so we don't include the uri in the affectsConfiguration() call
// (separately, just note that the settings change could be a global window setting from *.code-workspace file, rather than from settings.json)
const affected = event && event.affectsConfiguration("behave-vsc");
if (!affected && !forceFullRefresh && !testCfg)
return;
if (!testCfg)
config.logger.clearAllWksps();
// changing featuresPath in settings.json/*.vscode-workspace to a valid path, or adding/removing/renaming workspaces
// will not only change the set of workspaces we are watching, but also the output channels
config.logger.syncChannelsToWorkspaceFolders();
for (const wkspUri of getUrisOfWkspFoldersWithFeatures(true)) {
if (testCfg) {
if (urisMatch(testCfg.wkspUri, wkspUri)) {
config.reloadSettings(wkspUri, testCfg.testConfig);
}
continue;
}
config.reloadSettings(wkspUri);
const oldWatchers = wkspWatchers.get(wkspUri);
if (oldWatchers)
oldWatchers.forEach(w => w.dispose());
const watchers = startWatchingWorkspace(wkspUri, ctrl, testData, parser);
wkspWatchers.set(wkspUri, watchers);
watchers.forEach(w => context.subscriptions.push(w));
}
// configuration has now changed, e.g. featuresPath, so we need to reparse files
// (in the case of a testConfig insertion we just reparse the supplied workspace to avoid issues with parallel workspace integration test runs)
if (testCfg) {
parser.parseFilesForWorkspace(testCfg.wkspUri, testData, ctrl, "configurationChangedHandler", false);
return;
}
// we don't know which workspace was affected (see comment on affectsConfiguration above), so just reparse all workspaces
// (also, when a workspace is added/removed/renamed (forceRefresh), we need to clear down and reparse all test nodes to rebuild the top level nodes)
parser.clearTestItemsAndParseFilesForAllWorkspaces(testData, ctrl, "configurationChangedHandler", false);
}
catch (e: unknown) {
// entry point function (handler) - show error
config.logger.showError(e, undefined);
}
}
context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(async (event) => {
await configurationChangedHandler(event);
}));
diagLog(`perf info: activate took ${performance.now() - start} ms`);
return {
// return instances to support integration testing
runHandler: runHandler,
config: config,
ctrl: ctrl,
parser: parser,
getStepMappingsForStepsFileFunction: getStepMappingsForStepsFileFunction,
getStepFileStepForFeatureFileStep: getStepFileStepForFeatureFileStep,
testData: testData,
configurationChangedHandler: configurationChangedHandler
};
}
catch (e: unknown) {
// entry point function (handler) - show error
if (config && config.logger) {
config.logger.showError(e, undefined);
}
else {
// no logger, use vscode.window.showErrorMessage directly
const text = (e instanceof Error ? (e.stack ? e.stack : e.message) : e as string);
vscode.window.showErrorMessage(text);
}
}
} // end activate()