Skip to content

Commit 829a42a

Browse files
authored
Merge pull request #373 from codefori/fix/367_fix_hover_support
Adjust bracket positioning in SQL hover provider
2 parents d84ba86 + fc9d6a2 commit 829a42a

File tree

6 files changed

+196
-47
lines changed

6 files changed

+196
-47
lines changed

src/language/providers/hoverProvider.ts

+24-11
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import Statement from "../../database/statement";
66
import { getParmAttributes, prepareParamType } from "./logic/completion";
77
import { StatementType } from "../sql/types";
88
import { remoteAssistIsEnabled } from "./logic/available";
9-
import { getPositionData } from "./logic/callable";
109
import { CallableSignature } from "../../database/callable";
10+
import { getPositionData } from "../sql/document";
1111

1212
// =================================
1313
// We need to open provider to exist so symbols can be cached for hover support when opening files
@@ -87,16 +87,29 @@ export const hoverProvider = languages.registerHoverProvider({ language: `sql` }
8787

8888
if (result) {
8989
if ('routine' in result) {
90-
const routineOffset = ref.tokens[ref.tokens.length-1].range.end+1;
91-
const callableRef = statementAt.getCallableDetail(routineOffset, false);
92-
if (callableRef) {
93-
const { currentCount } = getPositionData(callableRef, routineOffset);
94-
const signatures = await DbCache.getCachedSignatures(callableRef.parentRef.object.schema, callableRef.parentRef.object.name);
95-
const possibleSignatures = signatures.filter((s) => s.parms.length >= currentCount).sort((a, b) => a.parms.length - b.parms.length);
96-
const signature = possibleSignatures.find((signature) => currentCount <= signature.parms.length);
97-
if (signature) {
98-
addRoutineMd(md, signature, result);
99-
}
90+
let signatures: CallableSignature[];
91+
let signature: CallableSignature | undefined;
92+
93+
const lastToken = ref.tokens[ref.tokens.length-1];
94+
95+
if (lastToken.type === `closebracket`) {
96+
let routineOffset: number = lastToken.range.start-1;
97+
const callableRef = statementAt.getCallableDetail(routineOffset, false);
98+
if (callableRef) {
99+
const { currentCount } = getPositionData(callableRef, routineOffset);
100+
signatures = await DbCache.getCachedSignatures(callableRef.parentRef.object.schema, callableRef.parentRef.object.name);
101+
const possibleSignatures = signatures.filter((s) => s.parms.length >= currentCount).sort((a, b) => a.parms.length - b.parms.length);
102+
signature = possibleSignatures.find((signature) => currentCount <= signature.parms.length);
103+
}
104+
}
105+
106+
if (!signature) {
107+
signatures = await DbCache.getCachedSignatures(result.routine.schema, result.routine.name);
108+
signature = signatures[0];
109+
}
110+
111+
if (signature) {
112+
addRoutineMd(md, signature, result);
100113
}
101114
} else {
102115
addSymbol(md, result);

src/language/providers/logic/callable.ts

+1-23
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import Statement from "../../../database/statement";
55
import { createCompletionItem, getParmAttributes } from "./completion";
66
import { DbCache } from "./cache";
77
import { SQLParm } from "../../../types";
8+
import { getPositionData } from "../../sql/document";
89

910
/**
1011
* Checks if the ref exists as a procedure or function. Then,
@@ -117,27 +118,4 @@ export function getCallableParameters(ref: CallableReference, offset: number): C
117118
});
118119
}
119120
return [];
120-
}
121-
122-
export function getPositionData(ref: CallableReference, offset: number) {
123-
const paramCommas = ref.tokens.filter(token => token.type === `comma`);
124-
125-
let currentParm = paramCommas.findIndex(t => offset < t.range.end);
126-
127-
if (currentParm === -1) {
128-
currentParm = paramCommas.length;
129-
}
130-
131-
const firstNamedPipe = ref.tokens.find((token, i) => token.type === `rightpipe`);
132-
let firstNamedParameter = firstNamedPipe ? paramCommas.findIndex((token, i) => token.range.start > firstNamedPipe.range.start) : undefined;
133-
134-
if (firstNamedParameter === -1) {
135-
firstNamedParameter = undefined;
136-
}
137-
138-
return {
139-
currentParm,
140-
currentCount: paramCommas.length + 1,
141-
firstNamedParameter
142-
};
143121
}

src/language/providers/parameterProvider.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { MarkdownString, ParameterInformation, Position, Range, SignatureHelp, SignatureInformation, TextEdit, languages } from "vscode";
22
import Statement from "../../database/statement";
3-
import Document from "../sql/document";
4-
import { getCallableParameters, getPositionData, isCallableType } from "./logic/callable";
5-
import { getParmAttributes, prepareParamType } from "./logic/completion";
3+
import Document, { getPositionData } from "../sql/document";
4+
import { isCallableType } from "./logic/callable";
5+
import { prepareParamType } from "./logic/completion";
66
import { CallableType } from "../../database/callable";
77
import { StatementType } from "../sql/types";
88
import { remoteAssistIsEnabled } from "./logic/available";
@@ -22,7 +22,7 @@ export const signatureProvider = languages.registerSignatureHelpProvider({ langu
2222
const currentStatement = sqlDoc.getStatementByOffset(offset);
2323

2424
if (currentStatement) {
25-
const routineType: CallableType = currentStatement.type === StatementType.Call ? `PROCEDURE` : `FUNCTION`;
25+
const routineType: CallableType = currentStatement. type === StatementType.Call ? `PROCEDURE` : `FUNCTION`;
2626
const callableRef = currentStatement.getCallableDetail(offset, true);
2727
// TODO: check the function actually exists before returning
2828
if (callableRef) {

src/language/sql/document.ts

+25-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import Statement from "./statement";
22
import SQLTokeniser from "./tokens";
3-
import { Definition, IRange, ParsedEmbeddedStatement, StatementGroup, StatementType, StatementTypeWord, Token } from "./types";
3+
import { CallableReference, Definition, IRange, ParsedEmbeddedStatement, StatementGroup, StatementType, StatementTypeWord, Token } from "./types";
44

55
export default class Document {
66
content: string;
@@ -261,6 +261,30 @@ export default class Document {
261261
}
262262
}
263263

264+
265+
export function getPositionData(ref: CallableReference, offset: number) {
266+
const paramCommas = ref.tokens.filter(token => token.type === `comma`);
267+
268+
let currentParm = paramCommas.findIndex(t => offset < t.range.start);
269+
270+
if (currentParm === -1) {
271+
currentParm = paramCommas.length;
272+
}
273+
274+
const firstNamedPipe = ref.tokens.find((token, i) => token.type === `rightpipe`);
275+
let firstNamedParameter = firstNamedPipe ? paramCommas.findIndex((token, i) => token.range.start > firstNamedPipe.range.start) : undefined;
276+
277+
if (firstNamedParameter === -1) {
278+
firstNamedParameter = undefined;
279+
}
280+
281+
return {
282+
currentParm,
283+
currentCount: paramCommas.length + 1,
284+
firstNamedParameter
285+
};
286+
}
287+
264288
function getSymbolsForStatements(statements: Statement[]) {
265289
let defintions: Definition[] = [];
266290

src/language/sql/statement.ts

+24-4
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,16 @@ export default class Statement {
117117
if (tokenInOffset) {
118118
if (tokenInOffset.type === `block`) {
119119
if (tokenInOffset.block!.length > 0) {
120-
return blockContainsOffset(cOffset, tokenInOffset.block!);
120+
let blockRange = blockContainsOffset(cOffset, tokenInOffset.block!);
121+
if (!blockRange) {
122+
blockRange = {
123+
start: this.tokens.findIndex(token => token.range.start === tokenInOffset.range.start) + 1,
124+
end: this.tokens.findIndex(token => token.range.end === tokenInOffset.range.end)
125+
}
126+
}
127+
128+
return blockRange;
129+
121130
} else {
122131
const rawEnd = this.tokens.findIndex(token => token.range.end === tokenInOffset.range.end);
123132
return {
@@ -378,7 +387,7 @@ export default class Statement {
378387
switch (this.type) {
379388
case StatementType.Call:
380389
// CALL X()
381-
doAdd(this.getRefAtToken(1));
390+
doAdd(this.getRefAtToken(1, {includeParameters: true}));
382391
break;
383392

384393
case StatementType.Alter:
@@ -550,7 +559,7 @@ export default class Statement {
550559

551560
let endIndex = i;
552561

553-
const isSubSelect = tokenIs(nextToken, `function`, `TABLE`) || tokenIs(nextToken, `function`, `LATERAL`) || (options.includeParameters && tokenIs(nextToken, `function`));
562+
const isSubSelect = (tokenIs(nextToken, `function`, `TABLE`) || tokenIs(nextToken, `function`, `LATERAL`) || (options.includeParameters && tokenIs(nextToken, `function`)) && this.type !== StatementType.Call);
554563

555564
if (isSubSelect) {
556565
sqlObj = this.getRefAtToken(i+2);
@@ -598,7 +607,6 @@ export default class Statement {
598607
}
599608

600609
if (sqlObj) {
601-
602610
if (options.withSystemName !== true) {
603611
// If the next token is not a clause.. we might have the alias
604612
if (nextToken && this.tokens[nextIndex+1]) {
@@ -615,6 +623,18 @@ export default class Statement {
615623

616624
if (!isSubSelect && !sqlObj.isUDTF) {
617625
sqlObj.tokens = this.tokens.slice(i, endIndex+1);
626+
627+
if (options.includeParameters && tokenIs(this.tokens[endIndex+1], `openbracket`)) {
628+
const blockTokens = this.getBlockAt(this.tokens[endIndex+1].range.end+1);
629+
630+
if (blockTokens.length > 0) {
631+
sqlObj.tokens = sqlObj.tokens.concat([
632+
{type: `openbracket`, value: `(`, range: {start: this.tokens[endIndex+1].range.start, end: this.tokens[endIndex+1].range.end}},
633+
...blockTokens,
634+
{type: `closebracket`, value: `)`, range: {start: blockTokens[blockTokens.length-1].range.end, end: blockTokens[blockTokens.length-1].range.end}}
635+
]);
636+
}
637+
}
618638
}
619639

620640
if (options.withSystemName) {

src/language/sql/tests/statements.test.ts

+118-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { assert, describe, expect, test } from 'vitest'
22
import SQLTokeniser from '../tokens'
3-
import Document from '../document';
4-
import { ClauseType, StatementType } from '../types';
3+
import Document, { getPositionData } from '../document';
4+
import { CallableReference, ClauseType, StatementType } from '../types';
55

66
const parserScenarios = describe.each([
77
{newDoc: (content: string) => new Document(content)},
@@ -480,9 +480,13 @@ parserScenarios(`Object references`, ({newDoc}) => {
480480

481481
const refsA = talksStatement.getObjectReferences();
482482
expect(refsA.length).toBe(1);
483-
expect(refsA[0].tokens.length).toBe(1);
483+
484484
expect(refsA[0].object.name).toBe(`create_Sql_sample`);
485485
expect(refsA[0].object.schema).toBeUndefined();
486+
487+
const tokens = refsA[0].tokens;
488+
expect(tokens.length).toBe(4); // Includes the parameter tokens since it's a call
489+
expect(tokens[tokens.length-1].type).toBe(`closebracket`);
486490
});
487491

488492
test(`CALL: simple qualified`, () => {
@@ -498,9 +502,13 @@ parserScenarios(`Object references`, ({newDoc}) => {
498502

499503
const refsA = talksStatement.getObjectReferences();
500504
expect(refsA.length).toBe(1);
501-
expect(refsA[0].tokens.length).toBe(3);
505+
502506
expect(refsA[0].object.name).toBe(`create_Sql_sample`);
503507
expect(refsA[0].object.schema).toBe(`"QSYS"`);
508+
509+
const tokens = refsA[0].tokens;
510+
expect(tokens.length).toBe(6); // Includes the parameter tokens since it's a call
511+
expect(tokens[tokens.length-1].type).toBe(`closebracket`);
504512
});
505513

506514
test(`ALTER: with reference`, () => {
@@ -1995,6 +2003,112 @@ describe(`Parameter statement tests`, () => {
19952003
expect(callableC.parentRef.object.schema).toBe(`qsys2`);
19962004
expect(callableC.parentRef.object.name).toBe(`create_abcd`);
19972005
});
2006+
2007+
test('Partial parameters 1: Position data for procedure call', () => {
2008+
const sql = `call qsys2.ifs_write('asdasd', )`;
2009+
2010+
const document = new Document(sql);
2011+
const statements = document.statements;
2012+
2013+
expect(statements.length).toBe(1);
2014+
2015+
const callableReference: CallableReference = statements[0].getCallableDetail(29);
2016+
expect(callableReference).toBeDefined();
2017+
expect(callableReference.parentRef.object.name).toBe(`ifs_write`);
2018+
expect(callableReference.parentRef.object.schema).toBe(`qsys2`);
2019+
2020+
const positionData = getPositionData(callableReference, 29);
2021+
expect(positionData).toBeDefined();
2022+
2023+
expect(positionData.currentParm).toBe(1);
2024+
expect(positionData.currentCount).toBe(2);
2025+
});
2026+
2027+
test('Partial parameters 1.2: Position data for procedure call', () => {
2028+
const sql = `call qsys2.ifs_write('asdasd', )`;
2029+
2030+
const document = new Document(sql);
2031+
const statements = document.statements;
2032+
2033+
expect(statements.length).toBe(1);
2034+
2035+
const callableReference: CallableReference = statements[0].getCallableDetail(31);
2036+
expect(callableReference).toBeDefined();
2037+
expect(callableReference.parentRef.object.name).toBe(`ifs_write`);
2038+
expect(callableReference.parentRef.object.schema).toBe(`qsys2`);
2039+
2040+
const positionData = getPositionData(callableReference, 31);
2041+
expect(positionData).toBeDefined();
2042+
2043+
expect(positionData.currentParm).toBe(1);
2044+
expect(positionData.currentCount).toBe(2);
2045+
});
2046+
2047+
test('Partial parameters 2: Position data for procedure call', () => {
2048+
const sql = `call qsys2.ifs_write('asdasd', 243)`;
2049+
2050+
const document = new Document(sql);
2051+
const statements = document.statements;
2052+
2053+
expect(statements.length).toBe(1);
2054+
2055+
const callableReference: CallableReference = statements[0].getCallableDetail(25);
2056+
expect(callableReference).toBeDefined();
2057+
expect(callableReference.parentRef.object.name).toBe(`ifs_write`);
2058+
expect(callableReference.parentRef.object.schema).toBe(`qsys2`);
2059+
2060+
const positionData = getPositionData(callableReference, 25);
2061+
expect(positionData).toBeDefined();
2062+
2063+
expect(positionData.currentParm).toBe(0);
2064+
expect(positionData.currentCount).toBe(2);
2065+
});
2066+
2067+
test('Partial parameters 3: Position data for procedure call', () => {
2068+
const sql = `call qsys2.ifs_write('asdasd', 243, )`;
2069+
2070+
const document = new Document(sql);
2071+
const statements = document.statements;
2072+
2073+
expect(statements.length).toBe(1);
2074+
2075+
const callableReference: CallableReference = statements[0].getCallableDetail(25);
2076+
expect(callableReference).toBeDefined();
2077+
expect(callableReference.parentRef.object.name).toBe(`ifs_write`);
2078+
expect(callableReference.parentRef.object.schema).toBe(`qsys2`);
2079+
2080+
const positionDataA = getPositionData(callableReference, 25);
2081+
expect(positionDataA).toBeDefined();
2082+
2083+
expect(positionDataA.currentParm).toBe(0);
2084+
expect(positionDataA.currentCount).toBe(3);
2085+
2086+
const positionDataB = getPositionData(callableReference, 29);
2087+
expect(positionDataB).toBeDefined();
2088+
2089+
expect(positionDataB.currentParm).toBe(1);
2090+
expect(positionDataB.currentCount).toBe(3);
2091+
});
2092+
2093+
test('Partial parameters 4: Position data for procedure call', () => {
2094+
const sql = `call qsys2.ifs_write('asdasd', 'asdasd', overwrite => 'asdad')`;
2095+
2096+
const document = new Document(sql);
2097+
const statements = document.statements;
2098+
2099+
expect(statements.length).toBe(1);
2100+
2101+
const callableReference: CallableReference = statements[0].getCallableDetail(50);
2102+
expect(callableReference).toBeDefined();
2103+
expect(callableReference.parentRef.object.name).toBe(`ifs_write`);
2104+
expect(callableReference.parentRef.object.schema).toBe(`qsys2`);
2105+
2106+
const positionDataA = getPositionData(callableReference, 50);
2107+
expect(positionDataA).toBeDefined();
2108+
2109+
expect(positionDataA.currentParm).toBe(2);
2110+
expect(positionDataA.currentCount).toBe(3);
2111+
});
19982112
});
19992113

20002114
describe(`Prefix tests`, () => {

0 commit comments

Comments
 (0)