Skip to content

Commit 395fcb8

Browse files
Emit enclosing ranges for declarations (#308)
The following declarations have enclosing ranges: - Named arrow function declaration - Function declaration - Class declaration - Method declaration - TS Interface declaration - Enum declarations - Type alias declarations There are no performance regressions based on rough benchmarks against sourcegraph/sourcegraph. Co-authored-by: Titouan Launay <titouan.launay@lleedpartners.com>
1 parent 8e37bb0 commit 395fcb8

File tree

12 files changed

+670
-103
lines changed

12 files changed

+670
-103
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// format-options: showRanges
2+
3+
interface Foo {
4+
bar: string
5+
test: () => void
6+
}
7+
8+
interface Single<T> {
9+
t: T
10+
}
11+
12+
enum SimpleEnum {
13+
Case1,
14+
Case2,
15+
}
16+
17+
type SimpleTypeAlias = SimpleEnum
18+
19+
type ComplexTypeAlias<T> = Single<Single<T>>
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"name": "enclosing-ranges-ts",
3+
"version": "1.0.0"
4+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"name": "enclosing-ranges",
3+
"version": "0.0.1"
4+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// format-options: showRanges
2+
3+
const test = () => {
4+
const a = 'a'
5+
const b = 'b'
6+
7+
return a + b
8+
}
9+
10+
function test2() {
11+
const a = 'a'
12+
const b = 'b'
13+
14+
return a + b
15+
}
16+
17+
class Test {
18+
constructor() {
19+
const a = 'a'
20+
const b = 'b'
21+
22+
return a + b
23+
}
24+
25+
test() {
26+
const a = 'a'
27+
const b = 'b'
28+
29+
return a + b
30+
}
31+
32+
static test() {
33+
const a = 'a'
34+
const b = 'b'
35+
36+
return a + b
37+
}
38+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{ "compilerOptions": { "allowJs": true } }
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// < definition enclosing-ranges-ts 1.0.0 `index.ts`/
2+
3+
// format-options: showRanges
4+
5+
// < start enclosing_range enclosing-ranges-ts 1.0.0 `index.ts`/Foo#
6+
// < start enclosing_range enclosing-ranges-ts 1.0.0 `index.ts`/
7+
interface Foo {
8+
// ^^^ definition enclosing-ranges-ts 1.0.0 `index.ts`/Foo#
9+
bar: string
10+
//^^^ definition enclosing-ranges-ts 1.0.0 `index.ts`/Foo#bar.
11+
test: () => void
12+
//^^^^ definition enclosing-ranges-ts 1.0.0 `index.ts`/Foo#test.
13+
}
14+
// < end enclosing_range enclosing-ranges-ts 1.0.0 `index.ts`/Foo#
15+
16+
// < start enclosing_range enclosing-ranges-ts 1.0.0 `index.ts`/Single#
17+
interface Single<T> {
18+
// ^^^^^^ definition enclosing-ranges-ts 1.0.0 `index.ts`/Single#
19+
// ^ definition enclosing-ranges-ts 1.0.0 `index.ts`/Single#[T]
20+
t: T
21+
//^ definition enclosing-ranges-ts 1.0.0 `index.ts`/Single#t.
22+
// ^ reference enclosing-ranges-ts 1.0.0 `index.ts`/Single#[T]
23+
}
24+
// < end enclosing_range enclosing-ranges-ts 1.0.0 `index.ts`/Single#
25+
26+
// < start enclosing_range enclosing-ranges-ts 1.0.0 `index.ts`/SimpleEnum#
27+
enum SimpleEnum {
28+
// ^^^^^^^^^^ definition enclosing-ranges-ts 1.0.0 `index.ts`/SimpleEnum#
29+
Case1,
30+
//^^^^^ definition enclosing-ranges-ts 1.0.0 `index.ts`/SimpleEnum#Case1.
31+
Case2,
32+
//^^^^^ definition enclosing-ranges-ts 1.0.0 `index.ts`/SimpleEnum#Case2.
33+
}
34+
// < end enclosing_range enclosing-ranges-ts 1.0.0 `index.ts`/SimpleEnum#
35+
36+
// < start enclosing_range enclosing-ranges-ts 1.0.0 `index.ts`/SimpleTypeAlias#
37+
type SimpleTypeAlias = SimpleEnum
38+
// ^^^^^^^^^^^^^^^ definition enclosing-ranges-ts 1.0.0 `index.ts`/SimpleTypeAlias#
39+
// ^^^^^^^^^^ reference enclosing-ranges-ts 1.0.0 `index.ts`/SimpleEnum#
40+
// < end enclosing_range enclosing-ranges-ts 1.0.0 `index.ts`/SimpleTypeAlias#
41+
42+
// < start enclosing_range enclosing-ranges-ts 1.0.0 `index.ts`/ComplexTypeAlias#
43+
type ComplexTypeAlias<T> = Single<Single<T>>
44+
// ^^^^^^^^^^^^^^^^ definition enclosing-ranges-ts 1.0.0 `index.ts`/ComplexTypeAlias#
45+
// ^ definition enclosing-ranges-ts 1.0.0 `index.ts`/ComplexTypeAlias#[T]
46+
// ^^^^^^ reference enclosing-ranges-ts 1.0.0 `index.ts`/Single#
47+
// ^^^^^^ reference enclosing-ranges-ts 1.0.0 `index.ts`/Single#
48+
// ^ reference enclosing-ranges-ts 1.0.0 `index.ts`/ComplexTypeAlias#[T]
49+
// < end enclosing_range enclosing-ranges-ts 1.0.0 `index.ts`/ComplexTypeAlias#
50+
51+
// < end enclosing_range enclosing-ranges-ts 1.0.0 `index.ts`/
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// < definition enclosing-ranges 0.0.1 `range.js`/
2+
3+
// format-options: showRanges
4+
5+
// < start enclosing_range enclosing-ranges 0.0.1 `range.js`/
6+
// ⌄ start enclosing_range enclosing-ranges 0.0.1 `range.js`/test.
7+
const test = () => {
8+
// ^^^^ definition enclosing-ranges 0.0.1 `range.js`/test.
9+
const a = 'a'
10+
// ^ definition local 2
11+
const b = 'b'
12+
// ^ definition local 5
13+
14+
return a + b
15+
// ^ reference local 2
16+
// ^ reference local 5
17+
}
18+
// ^ end enclosing_range enclosing-ranges 0.0.1 `range.js`/test.
19+
20+
// < start enclosing_range enclosing-ranges 0.0.1 `range.js`/test2().
21+
function test2() {
22+
// ^^^^^ definition enclosing-ranges 0.0.1 `range.js`/test2().
23+
const a = 'a'
24+
// ^ definition local 8
25+
const b = 'b'
26+
// ^ definition local 11
27+
28+
return a + b
29+
// ^ reference local 8
30+
// ^ reference local 11
31+
}
32+
// < end enclosing_range enclosing-ranges 0.0.1 `range.js`/test2().
33+
34+
// < start enclosing_range enclosing-ranges 0.0.1 `range.js`/Test#
35+
class Test {
36+
// ^^^^ definition enclosing-ranges 0.0.1 `range.js`/Test#
37+
constructor() {
38+
//^^^^^^^^^^^ definition enclosing-ranges 0.0.1 `range.js`/Test#`<constructor>`().
39+
const a = 'a'
40+
// ^ definition local 14
41+
const b = 'b'
42+
// ^ definition local 17
43+
44+
return a + b
45+
// ^ reference local 14
46+
// ^ reference local 17
47+
}
48+
49+
// ⌄ start enclosing_range enclosing-ranges 0.0.1 `range.js`/Test#test().
50+
test() {
51+
//^^^^ definition enclosing-ranges 0.0.1 `range.js`/Test#test().
52+
const a = 'a'
53+
// ^ definition local 20
54+
const b = 'b'
55+
// ^ definition local 23
56+
57+
return a + b
58+
// ^ reference local 20
59+
// ^ reference local 23
60+
}
61+
// ^ end enclosing_range enclosing-ranges 0.0.1 `range.js`/Test#test().
62+
63+
// ⌄ start enclosing_range enclosing-ranges 0.0.1 `range.js`/Test#test().
64+
static test() {
65+
// ^^^^ definition enclosing-ranges 0.0.1 `range.js`/Test#test().
66+
const a = 'a'
67+
// ^ definition local 26
68+
const b = 'b'
69+
// ^ definition local 29
70+
71+
return a + b
72+
// ^ reference local 26
73+
// ^ reference local 29
74+
}
75+
// ^ end enclosing_range enclosing-ranges 0.0.1 `range.js`/Test#test().
76+
}
77+
// < end enclosing_range enclosing-ranges 0.0.1 `range.js`/Test#
78+
79+
// < end enclosing_range enclosing-ranges 0.0.1 `range.js`/

src/Descriptor.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ type Suffix = scip.scip.Descriptor.Suffix
66
const Suffix = scip.scip.Descriptor.Suffix
77

88
export function packageDescriptor(name: string): Descriptor {
9-
return new Descriptor({ name, suffix: Suffix.Package })
9+
return new Descriptor({ name, suffix: Suffix.Namespace })
1010
}
1111

1212
export function typeDescriptor(name: string): Descriptor {
@@ -35,7 +35,7 @@ export function typeParameterDescriptor(name: string): Descriptor {
3535

3636
export function descriptorString(desc: Descriptor): string {
3737
switch (desc.suffix) {
38-
case Suffix.Package: {
38+
case Suffix.Namespace: {
3939
return escapedName(desc) + '/'
4040
}
4141
case Suffix.Type: {

src/FileIndexer.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ export class FileIndexer {
7171
this.pushOccurrence(
7272
new scip.scip.Occurrence({
7373
range: [0, 0, 0],
74+
enclosing_range: Range.fromNode(this.sourceFile).toLsif(),
7475
symbol: symbol.value,
7576
symbol_roles: scip.scip.SymbolRole.Definition,
7677
})
@@ -168,6 +169,27 @@ export class FileIndexer {
168169
for (const declaration of declarations) {
169170
let scipSymbol = this.scipSymbol(declaration)
170171

172+
let enclosingRange: number[] | undefined
173+
174+
if (!isDefinitionNode || scipSymbol.isEmpty() || scipSymbol.isLocal()) {
175+
// Skip enclosing ranges for these cases
176+
} else if (
177+
ts.isVariableDeclaration(declaration) &&
178+
declaration.initializer &&
179+
ts.isFunctionLike(declaration.initializer)
180+
) {
181+
enclosingRange = Range.fromNode(declaration.initializer).toLsif()
182+
} else if (
183+
ts.isFunctionDeclaration(declaration) ||
184+
ts.isEnumDeclaration(declaration) ||
185+
ts.isTypeAliasDeclaration(declaration) ||
186+
ts.isClassDeclaration(declaration) ||
187+
ts.isMethodDeclaration(declaration) ||
188+
ts.isInterfaceDeclaration(declaration)
189+
) {
190+
enclosingRange = Range.fromNode(declaration).toLsif()
191+
}
192+
171193
if (
172194
((ts.isIdentifier(node) && ts.isNewExpression(node.parent)) ||
173195
(ts.isPropertyAccessExpression(node.parent) &&
@@ -187,6 +209,7 @@ export class FileIndexer {
187209
}
188210
this.pushOccurrence(
189211
new scip.scip.Occurrence({
212+
enclosing_range: enclosingRange,
190213
range,
191214
symbol: scipSymbol.value,
192215
symbol_roles: role,

src/SnapshotTesting.ts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ export function formatSnapshot(
6767
externalSymbolTable.set(externalSymbol.symbol, externalSymbol)
6868
}
6969

70+
const enclosingRanges: { range: Range; symbol: string }[] = []
7071
const symbolsWithDefinitions: Set<string> = new Set()
7172

7273
const formatOptions = parseOptions(input.lines)
@@ -77,6 +78,29 @@ export function formatSnapshot(
7778
if (isDefinition) {
7879
symbolsWithDefinitions.add(occurrence.symbol)
7980
}
81+
82+
if (formatOptions.showRanges && occurrence.enclosing_range.length > 0) {
83+
enclosingRanges.push({
84+
range: Range.fromLsif(occurrence.enclosing_range),
85+
symbol: occurrence.symbol,
86+
})
87+
}
88+
}
89+
90+
enclosingRanges.sort(enclosingRangesByLine)
91+
92+
const enclosingRangeStarts: (typeof enclosingRanges)[number][][] = Array.from(
93+
new Array(input.lines.length),
94+
() => []
95+
)
96+
const enclosingRangeEnds: (typeof enclosingRanges)[number][][] = Array.from(
97+
new Array(input.lines.length),
98+
() => []
99+
)
100+
101+
for (const enclosingRange of enclosingRanges) {
102+
enclosingRangeStarts[enclosingRange.range.start.line].push(enclosingRange)
103+
enclosingRangeEnds[enclosingRange.range.end.line].unshift(enclosingRange)
80104
}
81105

82106
const emittedDocstrings: Set<string> = new Set()
@@ -162,6 +186,38 @@ export function formatSnapshot(
162186
out.push('\n')
163187
}
164188

189+
const pushEnclosingRange = (
190+
enclosingRange: {
191+
range: Range
192+
symbol: string
193+
},
194+
end: boolean = false
195+
): void => {
196+
if (!formatOptions.showRanges) {
197+
return
198+
}
199+
200+
out.push(commentSyntax)
201+
out.push(' '.repeat(Math.max(1, enclosingRange.range.start.character - 2)))
202+
203+
if (enclosingRange.range.start.character < 2) {
204+
out.push('<')
205+
} else if (end) {
206+
out.push('^')
207+
} else {
208+
out.push('⌄')
209+
}
210+
211+
if (end) {
212+
out.push(' end ')
213+
} else {
214+
out.push(' start ')
215+
}
216+
out.push('enclosing_range ')
217+
out.push(symbolNameForSnapshot(enclosingRange.symbol))
218+
out.push('\n')
219+
}
220+
165221
doc.occurrences.sort(occurrencesByLine)
166222
let occurrenceIndex = 0
167223

@@ -189,6 +245,11 @@ export function formatSnapshot(
189245
}
190246
}
191247

248+
// Check if any enclosing ranges start on this line
249+
for (const enclosingRange of enclosingRangeStarts[lineNumber]) {
250+
pushEnclosingRange(enclosingRange)
251+
}
252+
192253
out.push('')
193254
out.push(line)
194255
out.push('\n')
@@ -235,10 +296,29 @@ export function formatSnapshot(
235296

236297
pushDoc(range, occurrence.symbol, isDefinition, isStartOfLine)
237298
}
299+
300+
// Check if any enclosing ranges end on this line
301+
for (const enclosingRange of enclosingRangeEnds[lineNumber]) {
302+
pushEnclosingRange(enclosingRange, true)
303+
}
238304
}
239305
return out.join('')
240306
}
241307

242308
function occurrencesByLine(a: scip.Occurrence, b: scip.Occurrence): number {
243309
return Range.fromLsif(a.range).compare(Range.fromLsif(b.range))
244310
}
311+
312+
function enclosingRangesByLine(
313+
a: { range: Range; symbol: string },
314+
b: { range: Range; symbol: string }
315+
): number {
316+
// Return the range that starts first, and if they start at the same line, the one that ends last (enclosing).
317+
const rangeCompare = a.range.compare(b.range)
318+
319+
if (rangeCompare !== 0) {
320+
return rangeCompare
321+
}
322+
323+
return b.range.end.line - a.range.end.line
324+
}

0 commit comments

Comments
 (0)