Skip to content
This repository was archived by the owner on Aug 25, 2026. It is now read-only.

Commit c77cf1b

Browse files
committed
TML-2984: map interpreter spans via source offsets; family-neutral fixture stubs
The span-to-range mapping now recovers positions from the span offsets through SourceFile.positionAt -- the literal inverse of how rangeToPslSpan derives them -- which also keeps the framework packages family-vocabulary-clean. Test stubs stop borrowing family names for arbitrary strings. Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
1 parent 2a29f81 commit c77cf1b

4 files changed

Lines changed: 36 additions & 21 deletions

File tree

packages/1-framework/3-tooling/language-server/src/diagnostic-mapping.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type {
22
ContractSourceDiagnostic,
33
ContractSourceDiagnosticSpan,
44
} from '@prisma-next/config/config-types';
5-
import type { ParseDiagnostic, Range } from '@prisma-next/psl-parser/syntax';
5+
import type { ParseDiagnostic, Range, SourceFile } from '@prisma-next/psl-parser/syntax';
66

77
export const ParseDiagnosticSeverity = {
88
Error: 1,
@@ -38,20 +38,25 @@ const documentStartRange: Range = {
3838

3939
export function mapInterpreterDiagnostics(
4040
diagnostics: readonly ContractSourceDiagnostic[],
41+
sourceFile: SourceFile,
4142
): readonly LspDiagnostic[] {
4243
return diagnostics.map((diagnostic) => ({
43-
range: diagnostic.span === undefined ? documentStartRange : pslSpanToRange(diagnostic.span),
44+
range:
45+
diagnostic.span === undefined
46+
? documentStartRange
47+
: pslSpanToRange(diagnostic.span, sourceFile),
4448
message: diagnostic.message,
4549
code: diagnostic.code,
4650
severity: ParseDiagnosticSeverity.Error,
4751
}));
4852
}
4953

50-
// Inverse of psl-parser's rangeToPslSpan: spans carry 1-based line/column,
51-
// LSP ranges are 0-based line/character.
52-
function pslSpanToRange(span: ContractSourceDiagnosticSpan): Range {
54+
// Inverse of psl-parser's rangeToPslSpan, which derives every span position
55+
// from a source offset: recovering the 0-based LSP position from that same
56+
// offset round-trips exactly.
57+
function pslSpanToRange(span: ContractSourceDiagnosticSpan, sourceFile: SourceFile): Range {
5358
return {
54-
start: { line: span.start.line - 1, character: span.start.column - 1 },
55-
end: { line: span.end.line - 1, character: span.end.column - 1 },
59+
start: sourceFile.positionAt(span.start.offset),
60+
end: sourceFile.positionAt(span.end.offset),
5661
};
5762
}

packages/1-framework/3-tooling/language-server/src/project-artifacts.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,10 @@ export function createProjectArtifacts(options: ProjectArtifactsOptions): Projec
6767
},
6868
interpretation.context,
6969
);
70-
memo = mapInterpreterDiagnostics(result.ok ? [] : result.failure.diagnostics);
70+
memo = mapInterpreterDiagnostics(
71+
result.ok ? [] : result.failure.diagnostics,
72+
computed.sourceFile,
73+
);
7174
}
7275
return memo;
7376
};

packages/1-framework/3-tooling/language-server/test/config-resolution.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ function interpretCapableConfig(inputs: readonly string[]): PrismaNextConfig {
3535
function stubStackWithContext(): ControlStack {
3636
return {
3737
extensionPacks: [{ id: 'ext-a' }, { id: 'ext-b' }],
38-
extensionContracts: new Map([['ext-a', { targetFamily: 'sql' }]]),
38+
extensionContracts: new Map([['ext-a', { targetFamily: 'demo' }]]),
3939
scalarTypeDescriptors: new Map([['Int', 'int']]),
4040
authoringContributions: {
4141
field: {},
@@ -49,7 +49,7 @@ function stubStackWithContext(): ControlStack {
4949
defaultFunctionRegistry: new Map(),
5050
generatorDescriptors: [],
5151
},
52-
capabilities: { sql: { scalarList: true } },
52+
capabilities: { demo: { scalarList: true } },
5353
} as unknown as ControlStack;
5454
}
5555

packages/1-framework/3-tooling/language-server/test/diagnostic-mapping.test.ts

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,41 +34,48 @@ describe('mapParseDiagnostics', () => {
3434
});
3535

3636
describe('mapInterpreterDiagnostics', () => {
37-
it('maps a 1-based span to a 0-based LSP range (hand-computed)', () => {
37+
const text = 'model User {\n id Int @id\n posts Post[]\n}\n';
38+
const sourceFile = new SourceFile(text);
39+
40+
it('maps a span to a 0-based LSP range (hand-computed)', () => {
41+
// Offsets 28..33 sit on the third line (" posts Post[]"), landing on
42+
// "posts": 0-based positions {2,2}..{2,7}.
3843
const diagnostic: ContractSourceDiagnostic = {
3944
code: 'PSL_UNRESOLVED_RELATION',
4045
message: 'relation target not found',
4146
span: {
42-
start: { offset: 20, line: 3, column: 3 },
43-
end: { offset: 27, line: 3, column: 10 },
47+
start: { offset: 28, line: 3, column: 3 },
48+
end: { offset: 33, line: 3, column: 8 },
4449
},
4550
};
4651

47-
const [mapped] = mapInterpreterDiagnostics([diagnostic]);
52+
const [mapped] = mapInterpreterDiagnostics([diagnostic], sourceFile);
4853

4954
expect(mapped).toEqual({
50-
range: { start: { line: 2, character: 2 }, end: { line: 2, character: 9 } },
55+
range: { start: { line: 2, character: 2 }, end: { line: 2, character: 7 } },
5156
message: 'relation target not found',
5257
code: 'PSL_UNRESOLVED_RELATION',
5358
severity: ParseDiagnosticSeverity.Error,
5459
});
5560
});
5661

5762
it('inverts rangeToPslSpan for a span produced from a real source file', () => {
58-
const text = 'model User {\n id Int @id\n posts Post[]\n}\n';
59-
const sourceFile = new SourceFile(text);
6063
const range = { start: { line: 2, character: 2 }, end: { line: 2, character: 7 } };
6164
const span = rangeToPslSpan(range, sourceFile);
6265

63-
const [mapped] = mapInterpreterDiagnostics([{ code: 'PSL_DEMO', message: 'demo', span }]);
66+
const [mapped] = mapInterpreterDiagnostics(
67+
[{ code: 'PSL_DEMO', message: 'demo', span }],
68+
sourceFile,
69+
);
6470

6571
expect(mapped?.range).toEqual(range);
6672
});
6773

6874
it('anchors a span-less diagnostic at document start instead of dropping it', () => {
69-
const [mapped] = mapInterpreterDiagnostics([
70-
{ code: 'PSL_SPANLESS', message: 'no span available' },
71-
]);
75+
const [mapped] = mapInterpreterDiagnostics(
76+
[{ code: 'PSL_SPANLESS', message: 'no span available' }],
77+
sourceFile,
78+
);
7279

7380
expect(mapped).toEqual({
7481
range: { start: { line: 0, character: 0 }, end: { line: 0, character: 1 } },

0 commit comments

Comments
 (0)