Skip to content

Commit b8475bf

Browse files
authored
Fix configuration path diagnostic ranges (#14679)
1 parent af190d8 commit b8475bf

3 files changed

Lines changed: 36 additions & 7 deletions

File tree

Extension/src/LanguageServer/configurations.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import * as vscode from 'vscode';
1414
import * as nls from 'vscode-nls';
1515
import * as which from 'which';
1616
import { logAndReturn, returns } from '../Utility/Async/returns';
17-
import { escapePathForSquiggles } from '../Utility/Text/escape';
17+
import { escapePathForSquiggles, getTextMatchOffsets } from '../Utility/Text/escape';
1818
import * as util from '../common';
1919
import { isWindows } from '../constants';
2020
import { getOutputChannelLogger } from '../logger';
@@ -2134,8 +2134,7 @@ export class CppProperties {
21342134
expandedPaths = result ?? [];
21352135
if (duration > 10 && configMatches) {
21362136
newSquiggleMetrics.SlowPathResolution++;
2137-
const curOffset = curText.indexOf(configMatches[0]);
2138-
const endOffset = curOffset + curPath.length;
2137+
const [curOffset, endOffset] = getTextMatchOffsets(curText, configMatches[0]);
21392138
const diagnostic: vscode.Diagnostic = new vscode.Diagnostic(
21402139
new vscode.Range(document.positionAt(curTextStartOffset + curOffset), document.positionAt(curTextStartOffset + endOffset)),
21412140
localize('resolve.path.took.too.long', "Path took {0}s to evaluate", duration),
@@ -2145,8 +2144,7 @@ export class CppProperties {
21452144
} catch (e) {
21462145
expandedPaths = [];
21472146
if (configMatches) {
2148-
const curOffset = curText.indexOf(configMatches[0]);
2149-
const endOffset = curOffset + curPath.length;
2147+
const [curOffset, endOffset] = getTextMatchOffsets(curText, configMatches[0]);
21502148
const diagnostic: vscode.Diagnostic = new vscode.Diagnostic(
21512149
new vscode.Range(document.positionAt(curTextStartOffset + curOffset), document.positionAt(curTextStartOffset + endOffset)),
21522150
localize('resolve.path.failed', "Failed to resolve path {0}. Error: {1}", curPath, (e as Error).message),

Extension/src/Utility/Text/escape.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,8 @@ export function escapePathForSquiggles(s: string): string {
77
return s.replace(/[-"\/\\^$*+?.()|[\]{}]/g, (character: string): string =>
88
character === '"' ? '\\\\"' : `\\${character}`);
99
}
10+
11+
export function getTextMatchOffsets(text: string, match: string): [number, number] {
12+
const startOffset: number = text.indexOf(match);
13+
return [startOffset, startOffset + match.length];
14+
}

Extension/test/unit/escape.test.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
* ------------------------------------------------------------------------------------------ */
55

66
import { describe, it } from 'mocha';
7-
import { doesNotMatch, match, strictEqual } from 'node:assert';
8-
import { escapePathForSquiggles } from '../../src/Utility/Text/escape';
7+
import { deepStrictEqual, doesNotMatch, match, ok, strictEqual } from 'node:assert';
8+
import { escapePathForSquiggles, getTextMatchOffsets } from '../../src/Utility/Text/escape';
99

1010
describe('Text escaping', () => {
1111
it('escapes paths for matching their JSON spelling', () => {
@@ -29,4 +29,30 @@ describe('Text escaping', () => {
2929
doesNotMatch(String.raw`C:\sdk\[headers]+(x)?.h\say\"hello\"and\"goodbye`, pattern);
3030
doesNotMatch(String.raw`C:\\sdk\\headers+(x)?.h\\say\"hello\"and\"goodbye`, pattern);
3131
});
32+
33+
it('uses the full source match for a non-first semicolon-delimited path', () => {
34+
const parsedPath: string = 'second';
35+
const sourceMatch: string = '"first;second;third"';
36+
const text: string = `"includePath": [${sourceMatch}]`;
37+
const pattern: RegExp = new RegExp(`"[^"]*?(?<="|;)${escapePathForSquiggles(parsedPath)}(?="|;).*?"`, 'g');
38+
const matches: string[] | null = text.match(pattern);
39+
40+
ok(matches);
41+
strictEqual(matches?.[0], sourceMatch);
42+
const startOffset: number = text.indexOf(sourceMatch);
43+
deepStrictEqual(getTextMatchOffsets(text, matches[0]), [startOffset, startOffset + sourceMatch.length]);
44+
});
45+
46+
it('uses the JSON source length when it differs from the parsed path', () => {
47+
const parsedPath: string = 'folder"quoted"';
48+
const sourceMatch: string = String.raw`"folder\"quoted\""`;
49+
const text: string = `"includePath": [${sourceMatch}]`;
50+
const pattern: RegExp = new RegExp(`"[^"]*?(?<="|;)${escapePathForSquiggles(parsedPath)}(?="|;).*?"`, 'g');
51+
const matches: string[] | null = text.match(pattern);
52+
53+
ok(matches);
54+
strictEqual(matches?.[0], sourceMatch);
55+
const startOffset: number = text.indexOf(sourceMatch);
56+
deepStrictEqual(getTextMatchOffsets(text, matches[0]), [startOffset, startOffset + sourceMatch.length]);
57+
});
3258
});

0 commit comments

Comments
 (0)