Skip to content

Commit a0ccd7d

Browse files
forivallacao
authored andcommitted
perf(language-service): re-use token locations when available
1 parent f47b9b0 commit a0ccd7d

File tree

5 files changed

+76
-11
lines changed

5 files changed

+76
-11
lines changed

packages/graphql-language-service/src/interface/getDefinition.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import {
2525

2626
import { Definition, FragmentInfo, Uri, ObjectTypeInfo } from '../types';
2727

28-
import { locToRange, offsetToPosition, Range, Position } from '../utils';
28+
import { locToRange, locStartToPosition, Range, Position } from '../utils';
2929
// import { getTypeInfo } from './getAutocompleteSuggestions';
3030

3131
export type DefinitionQueryResult = {
@@ -56,7 +56,7 @@ function getRange(text: string, node: ASTNode): Range {
5656
function getPosition(text: string, node: ASTNode): Position {
5757
const location = node.loc!;
5858
assert(location, 'Expected ASTNode to have a location.');
59-
return offsetToPosition(text, location.start);
59+
return locStartToPosition(text, location);
6060
}
6161

6262
export async function getDefinitionQueryResultForNamedType(

packages/graphql-language-service/src/interface/getOutline.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ import {
4141
} from 'graphql';
4242
import type { ASTReducer } from 'graphql/language/visitor';
4343

44-
import { offsetToPosition } from '../utils';
44+
import { locToRange } from '../utils';
4545

4646
const { INLINE_FRAGMENT } = Kind;
4747

@@ -133,10 +133,11 @@ function outlineTreeConverter(docText: string): OutlineTreeConverterType {
133133
DocumentNode | SelectionSetNode | NameNode | InlineFragmentNode
134134
>;
135135
const meta = (node: ExclusiveUnion<MetaNode>): OutlineTreeResultMeta => {
136+
const range = locToRange(docText, node.loc!);
136137
return {
137138
representativeName: node.name,
138-
startPosition: offsetToPosition(docText, node.loc!.start),
139-
endPosition: offsetToPosition(docText, node.loc!.end),
139+
startPosition: range.start,
140+
endPosition: range.end,
140141
kind: node.kind,
141142
children:
142143
node.selectionSet || node.fields || node.values || node.arguments || [],

packages/graphql-language-service/src/utils/Range.ts

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,37 @@ export function offsetToPosition(text: string, loc: number): Position {
6666
return new Position(lines, loc - lastLineIndex - 1);
6767
}
6868

69+
export function locStartToPosition(text: string, loc: Location) {
70+
return loc.startToken?.start === loc.start
71+
? new Position(loc.startToken.line - 1, loc.startToken.column - 1)
72+
: offsetToPosition(text, loc.start);
73+
}
74+
75+
function locEndToPosition(text: string, loc: Location) {
76+
if (loc.startToken?.start !== loc.start) {
77+
return offsetToPosition(text, loc.end);
78+
}
79+
const EOL = '\n';
80+
const buf = text.slice(loc.endToken.start, loc.endToken.end);
81+
const lastLineIndex = buf.lastIndexOf(EOL);
82+
if (lastLineIndex === -1) {
83+
return new Position(
84+
loc.endToken.line - 1,
85+
loc.endToken.column + buf.length - 1,
86+
);
87+
}
88+
const lines = buf.split(EOL).length - 1;
89+
return new Position(
90+
loc.endToken.line - 1 + lines,
91+
loc.endToken.end - loc.endToken.start - lastLineIndex - 1,
92+
);
93+
}
94+
6995
export function locToRange(text: string, loc: Location): Range {
70-
const start = offsetToPosition(text, loc.start);
71-
const end = offsetToPosition(text, loc.end);
96+
const start = locStartToPosition(text, loc);
97+
const end =
98+
loc.endToken?.end === loc.end
99+
? locEndToPosition(text, loc)
100+
: offsetToPosition(text, loc.end);
72101
return new Range(start, end);
73102
}

packages/graphql-language-service/src/utils/__tests__/Range.test.ts

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,18 @@
77
*
88
*/
99

10-
import { Location } from 'graphql';
10+
import { Location, parse, getOperationAST, FieldNode } from 'graphql';
1111
import { Range, Position, offsetToPosition, locToRange } from '../Range';
1212

1313
const text = `query test {
1414
name
1515
}`;
16+
const parsed = parse(text);
17+
const nameNode = getOperationAST(parsed)!.selectionSet.selections[0];
1618

1719
const absRange: Location = {
1820
start: 15,
19-
end: 18,
21+
end: 19,
2022
// @ts-ignore
2123
startToken: null,
2224
// @ts-ignore
@@ -26,7 +28,7 @@ const absRange: Location = {
2628
}; // position of 'name' attribute in the test query
2729

2830
const offsetRangeStart = new Position(1, 2);
29-
const offsetRangeEnd = new Position(1, 5);
31+
const offsetRangeEnd = new Position(1, 6);
3032

3133
describe('Position', () => {
3234
it('constructs a IPosition object', () => {
@@ -87,4 +89,31 @@ describe('locToRange()', () => {
8789
expect(range.end.character).toEqual(offsetRangeEnd.character);
8890
expect(range.end.line).toEqual(offsetRangeEnd.line);
8991
});
92+
it('returns the range for a location from a parsed node', () => {
93+
const range = locToRange(text, nameNode.loc!);
94+
expect(range.start.character).toEqual(offsetRangeStart.character);
95+
expect(range.start.line).toEqual(offsetRangeStart.line);
96+
expect(range.end.character).toEqual(offsetRangeEnd.character);
97+
expect(range.end.line).toEqual(offsetRangeEnd.line);
98+
});
99+
it('returns the same range as offsetToPosition with multiline token', () => {
100+
const blockText = `mutation test {
101+
saveMarkdown(markdown: """
102+
* block
103+
* multiline
104+
* string
105+
""")
106+
}`;
107+
const blockParsed = parse(blockText);
108+
const fieldNode = getOperationAST(blockParsed)!.selectionSet
109+
.selections[0] as FieldNode;
110+
const argumentNode = fieldNode.arguments![0];
111+
const startPosition = offsetToPosition(blockText, argumentNode.loc!.start);
112+
const endPosition = offsetToPosition(blockText, argumentNode.loc!.end);
113+
const range = locToRange(blockText, argumentNode.loc!);
114+
expect(range.start.character).toEqual(startPosition.character);
115+
expect(range.start.line).toEqual(startPosition.line);
116+
expect(range.end.character).toEqual(endPosition.character);
117+
expect(range.end.line).toEqual(endPosition.line);
118+
});
90119
});

packages/graphql-language-service/src/utils/index.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,13 @@ export {
2121

2222
export { getASTNodeAtPosition, pointToOffset } from './getASTNodeAtPosition';
2323

24-
export { Position, Range, locToRange, offsetToPosition } from './Range';
24+
export {
25+
Position,
26+
Range,
27+
locStartToPosition,
28+
locToRange,
29+
offsetToPosition,
30+
} from './Range';
2531

2632
export { validateWithCustomRules } from './validateWithCustomRules';
2733

0 commit comments

Comments
 (0)