Skip to content

Commit 555b92a

Browse files
committed
Add diagnostics for duplicated scenarios
1 parent e9ff366 commit 555b92a

3 files changed

Lines changed: 41 additions & 6 deletions

File tree

src/configuration.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export interface Configuration {
88
integrationTestRun: boolean;
99
readonly extensionTempFilesUri: vscode.Uri;
1010
readonly logger: Logger;
11+
readonly diagnostics: vscode.DiagnosticCollection;
1112
readonly workspaceSettings: { [wkspUriPath: string]: WorkspaceSettings };
1213
readonly globalSettings: WindowSettings;
1314
reloadSettings(wkspUri: vscode.Uri, testConfig?: vscode.WorkspaceConfiguration): void;
@@ -22,13 +23,15 @@ class ExtensionConfiguration implements Configuration {
2223
public exampleProject = false;
2324
public readonly extensionTempFilesUri;
2425
public readonly logger: Logger;
26+
public readonly diagnostics: vscode.DiagnosticCollection;
2527
private static _configuration?: ExtensionConfiguration;
2628
private _windowSettings: WindowSettings | undefined = undefined;
2729
private _resourceSettings: { [wkspUriPath: string]: WorkspaceSettings } = {};
2830

2931
private constructor() {
3032
ExtensionConfiguration._configuration = this;
3133
this.logger = new Logger();
34+
this.diagnostics = vscode.languages.createDiagnosticCollection("behave-vsc");
3235
this.extensionTempFilesUri = vscode.Uri.joinPath(vscode.Uri.file(os.tmpdir()), "behave-vsc");
3336
this.exampleProject = (vscode.workspace.workspaceFolders?.find(f =>
3437
f.uri.path.includes("/behave-vsc/example-projects/")) !== undefined);
@@ -37,6 +40,7 @@ class ExtensionConfiguration implements Configuration {
3740

3841
public dispose() {
3942
this.logger.dispose();
43+
this.diagnostics.dispose();
4044
}
4145

4246
static get configuration() {

src/extension.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<TestSu
7878
ctrl,
7979
treeView,
8080
config,
81+
config.diagnostics,
8182
cleanExtensionTempDirectoryCancelSource,
8283
junitWatcher,
8384
vscode.commands.registerTextEditorCommand(`behave-vsc.gotoStep`, gotoStepHandler),

src/parsers/testFile.ts

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,29 @@ export type TestData = WeakMap<vscode.TestItem, BehaveTestData>;
1313
export class TestFile {
1414
public didResolve = false;
1515

16+
private addDuplicateScenarioDiagnostics(featureUri: vscode.Uri, scenarioRanges: Map<string, vscode.Range[]>) {
17+
const existingDiagnostics = config.diagnostics.get(featureUri) || [];
18+
const newDiagnostics = [...existingDiagnostics];
19+
20+
for (const [scenarioName, ranges] of scenarioRanges) {
21+
if (ranges.length > 1) {
22+
diagLog(`Duplicate scenario detected: "${scenarioName}"`);
23+
24+
for (const range of ranges) {
25+
const diagnostic = new vscode.Diagnostic(
26+
range,
27+
`Duplicate scenario name: "${scenarioName}". Each scenario in a feature file must have a unique name.`,
28+
vscode.DiagnosticSeverity.Error
29+
);
30+
diagnostic.code = "duplicate-scenario";
31+
diagnostic.source = "behave-vsc";
32+
newDiagnostics.push(diagnostic);
33+
}
34+
}
35+
}
36+
config.diagnostics.set(featureUri, newDiagnostics);
37+
}
38+
1639
public async createScenarioTestItemsFromFeatureFileContent(wkspSettings: WorkspaceSettings, content: string, testData: TestData,
1740
controller: vscode.TestController, item: vscode.TestItem, caller: string) {
1841
if (!item.uri)
@@ -31,8 +54,14 @@ export class TestFile {
3154

3255
const thisGeneration = generationCounter++;
3356
const ancestors: { item: vscode.TestItem, children: vscode.TestItem[] }[] = [];
57+
const scenarioRanges = new Map<string, vscode.Range[]>();
3458
this.didResolve = true;
3559

60+
// Clear any existing diagnostics for this file
61+
const existingDiagnostics = config.diagnostics.get(featureUri) || [];
62+
const nonDuplicateDiagnostics = existingDiagnostics.filter(d => d.code !== "duplicate-scenario");
63+
config.diagnostics.set(featureUri, nonDuplicateDiagnostics);
64+
3665
const ascend = (depth: number) => {
3766
while (ancestors.length > depth) {
3867
const finished = ancestors.pop();
@@ -42,13 +71,9 @@ export class TestFile {
4271
finished.item.children.replace(finished.children);
4372
}
4473
catch (e: unknown) {
45-
let err = (e as Error).toString();
74+
const err = (e as Error).toString();
4675
if (err.includes("duplicate test item")) {
47-
const n = err.lastIndexOf('/');
48-
const scen = err.substring(n);
49-
err = err.replace(scen, `. Duplicate scenario name: "${scen.slice(1)}".`);
50-
// don't throw here, show it and carry on
51-
config.logger.showError(err, wkspSettings.uri);
76+
this.addDuplicateScenarioDiagnostics(featureUri, scenarioRanges);
5277
}
5378
else
5479
throw e;
@@ -59,6 +84,11 @@ export class TestFile {
5984
const onScenarioLine = (range: vscode.Range, scenarioName: string, isOutline: boolean) => {
6085
const parent = ancestors[ancestors.length - 1];
6186

87+
// Track scenario name and range for duplicate detection
88+
const ranges = scenarioRanges.get(scenarioName) || [];
89+
ranges.push(range);
90+
scenarioRanges.set(scenarioName, ranges);
91+
6292
const data = new Scenario(featureFilename, featureFileWkspRelativePath, featureName, scenarioName, thisGeneration, isOutline);
6393
const id = `${uriId(featureUri)}/${data.getLabel()}`;
6494
const tcase = controller.createTestItem(id, data.getLabel(), featureUri);

0 commit comments

Comments
 (0)