diff --git a/src/language/linter.js b/src/language/linter.js index 56f8210c..a5a53ae5 100644 --- a/src/language/linter.js +++ b/src/language/linter.js @@ -877,7 +877,7 @@ module.exports = class Linter { } let defRef; - if (currentProcedure) { + if (currentProcedure && currentProcedure.scope) { defRef = currentProcedure.scope.find(upperName); if (!defRef) { diff --git a/src/language/parser.js b/src/language/parser.js index bf70f51a..23411d3d 100644 --- a/src/language/parser.js +++ b/src/language/parser.js @@ -572,6 +572,11 @@ module.exports = class Parser { line: lineNumber } + currentItem.range = { + start: lineNumber, + end: null + }; + currentGroup = `structs`; // Expand the LIKEDS value if there is one. @@ -579,6 +584,7 @@ module.exports = class Parser { // Does the keywords include a keyword that makes end-ds useless? if (currentItem.keywords.some(keyword => oneLineTriggers[`DCL-DS`].some(trigger => keyword.startsWith(trigger)))) { + currentItem.range.end = lineNumber; scope.structs.push(currentItem); } else { currentItem.readParms = true; @@ -594,6 +600,11 @@ module.exports = class Parser { break; case `END-DS`: + if (dsScopes.length > 0) { + const currentDs = dsScopes[dsScopes.length - 1]; + currentDs.range.end = lineNumber; + } + if (dsScopes.length === 1) { scope.structs.push(dsScopes.pop()); } else @@ -619,8 +630,14 @@ module.exports = class Parser { currentItem.readParms = true; + currentItem.range = { + start: lineNumber, + end: null + }; + // Does the keywords include a keyword that makes end-ds useless? if (currentItem.keywords.some(keyword => oneLineTriggers[`DCL-PR`].some(trigger => keyword.startsWith(trigger)))) { + currentItem.range.end = lineNumber; scope.procedures.push(currentItem); resetDefinition = true; } @@ -632,6 +649,7 @@ module.exports = class Parser { case `END-PR`: if (currentItem && currentItem.type === `procedure`) { + currentItem.range.end = lineNumber; scope.procedures.push(currentItem); resetDefinition = true; } @@ -1002,6 +1020,11 @@ module.exports = class Parser { line: lineNumber } + currentItem.range = { + start: lineNumber, + end: null + }; + expandDs(file, currentItem); currentGroup = `structs`; @@ -1020,6 +1043,11 @@ module.exports = class Parser { path: file, line: lineNumber } + + currentItem.range = { + start: lineNumber, + end: lineNumber + }; currentGroup = `procedures`; scope.procedures.push(currentItem); @@ -1040,6 +1068,7 @@ module.exports = class Parser { break; default: + // No type, must be either a struct subfield OR a parameter if (!currentItem) { switch (currentGroup) { case `structs`: @@ -1088,6 +1117,8 @@ module.exports = class Parser { currentItem.subItems[currentItem.subItems.length - 1].keywords.push(Fixed.getPrettyType(dSpec), ...dSpec.keywords); } } + + currentItem.range.end = lineNumber; } break; } diff --git a/src/vscode/LanguageWorker.js b/src/vscode/LanguageWorker.js index 158d8c10..47ace137 100644 --- a/src/vscode/LanguageWorker.js +++ b/src/vscode/LanguageWorker.js @@ -225,11 +225,11 @@ module.exports = class LanguageWorker { currentScopeDefs.push( ...scope.subroutines.filter(sub => sub.position && sub.position.path === currentPath) .map(def => new vscode.DocumentSymbol( - def.name, + def.name, def.keywords.join(` `).trim(), vscode.SymbolKind.Function, - new vscode.Range(def.position.line, 0, def.position.line, 0), - new vscode.Range(def.position.line, 0, def.position.line, 0) + new vscode.Range(def.range.start, 0, def.range.end, 0), + new vscode.Range(def.range.start, 0, def.range.start, 0), )), ...scope.variables @@ -300,8 +300,8 @@ module.exports = class LanguageWorker { struct.name, struct.keywords.join(` `).trim(), vscode.SymbolKind.Struct, - new vscode.Range(struct.position.line, 0, struct.position.line, 0), - new vscode.Range(struct.position.line, 0, struct.position.line, 0) + new vscode.Range(struct.range.start, 0, struct.range.end, 0), + new vscode.Range(struct.range.start, 0, struct.range.start, 0), ); structDef.children.push( @@ -333,8 +333,8 @@ module.exports = class LanguageWorker { proc.name, proc.keywords.join(` `).trim(), vscode.SymbolKind.Function, - new vscode.Range(proc.position.line, 0, proc.position.line, 0), - new vscode.Range(proc.position.line, 0, proc.position.line, 0) + new vscode.Range(proc.range.start, 0, proc.range.end, 0), + new vscode.Range(proc.range.start, 0, proc.range.start, 0), ); procDef.children.push( diff --git a/tests/suite/basics.js b/tests/suite/basics.js index 90fde1e9..8e1108fd 100644 --- a/tests/suite/basics.js +++ b/tests/suite/basics.js @@ -65,6 +65,11 @@ exports.test3 = async () => { assert.strictEqual(cache.variables[0].position.line, 1, `Index of 1 expected`); assert.strictEqual(cache.variables[1].position.line, 6, `Index of 6 expected`); assert.strictEqual(cache.structs[0].position.line, 2, `Index of 2 expected`); + + assert.deepStrictEqual(cache.structs[0].range, { + start: 2, + end: 5 + }); } /** @@ -89,6 +94,7 @@ exports.test4 = async () => { assert.strictEqual(cache.variables[0].position.line, 1, `Index of 1 expected`); assert.strictEqual(cache.subroutines[0].range.start, 4, `Index of 4 expected`); + assert.strictEqual(cache.subroutines[0].range.end, 6); } /** @@ -363,6 +369,11 @@ exports.test12 = async () => { const theLocalProc = cache.find(`theLocalProc`); + assert.deepStrictEqual(theLocalProc.range, { + start: 16, + end: 23 + }); + // Has a parameter assert.strictEqual(theLocalProc.subItems.length, 1, `Expect length of 1`); @@ -437,4 +448,81 @@ exports.subds1 = async () => { assert.strictEqual(DsChangingNodeRole.subItems.length, 13); assert.strictEqual(DsChangingNodeRole.subItems[12].name, `Role`); -}; \ No newline at end of file + + assert.deepStrictEqual(DsChangingNodeRole.range, { + start: 2, + end: 19 + }); +}; + +exports.range1 = async () => { + const lines = [ + `**FREE`, + `///`, + `// Get delimiters`, + `//`, + `// Returns a pointer to the used delimiters for parsing the JSON and XML data.`, + `// The pointer points to a data structure in the format jx_DelimiterDS.`, + `//`, + `// @return Pointer to the delimiters data structure (jx_DelimiterDS)`, + `//`, + `// @warning Do not deallocate the returned pointer!`, + `///`, + `Dcl-PR json_getDelims Pointer extproc(*CWIDEN : 'jx_GetDelimiters') End-PR;`, + ``, + `///`, + `// Set delimiters`, + `//`, + `// Sets the delimiters used for parsing the JSON and XML data.`, + `// For the default delimiters see constant json_DELIMITERS.`, + `//`, + `// @param Delimiters`, + `///`, + `Dcl-PR json_setDelimiters extproc(*CWIDEN : 'jx_SetDelimiters');`, + ` delimiters pointer value options(*string);`, + `End-PR;`, + ``, + ].join(`\n`); + + const parser = new Parser(); + const cache = await parser.getDocs(uri, lines); + + assert.strictEqual(cache.procedures.length, 2); + + const json_getDelims = cache.find(`json_getDelims`); + assert.deepStrictEqual(json_getDelims.range, { + start: 11, + end: 11 + }); + + const json_getDelimiters = cache.find(`json_setDelimiters`); + assert.strictEqual(json_getDelimiters.subItems.length, 1); + assert.deepStrictEqual(json_getDelimiters.range, { + start: 21, + end: 23 + }); +}; + +exports.range2 = async () => { + const lines = [ + `**free`, + `Dcl-S FullCmd Char(32);`, + `Dcl-DS ABCD LikeDS(BBOOP);`, + ``, + ``, + `Dcl-S Eod Ind;`, + ].join(`\n`); + + const parser = new Parser(); + const cache = await parser.getDocs(uri, lines); + + assert.strictEqual(cache.variables.length, 2); + assert.strictEqual(cache.structs.length, 1); + + const ABCD = cache.find(`ABCD`); + assert.strictEqual(ABCD.keyword[`LIKEDS`], `BBOOP`); + assert.deepStrictEqual(ABCD.range, { + start: 2, + end: 2 + }); +} \ No newline at end of file diff --git a/tests/suite/fixed.js b/tests/suite/fixed.js index 5caeaab9..e4061f8d 100644 --- a/tests/suite/fixed.js +++ b/tests/suite/fixed.js @@ -406,6 +406,9 @@ exports.fixed10 = async () => { assert.strictEqual(dataDs.name, `data`); assert.strictEqual(dataDs.subItems.length, 4); + assert.strictEqual(dataDs.range.start, 0); + assert.strictEqual(dataDs.range.end, 4); + const rrn02 = cache.find(`rrn02`); assert.strictEqual(rrn02.name, `rrn02`); assert.strictEqual(rrn02.keywords.includes(`PACKED(7:2)`), true); @@ -485,6 +488,8 @@ exports.fixedfree1 = async () => { const Obj_List = cache.find(`Obj_List`); assert.strictEqual(Obj_List.name, `Obj_List`); + assert.strictEqual(Obj_List.range.start, 6); + assert.strictEqual(Obj_List.range.end, 50); assert.strictEqual(Obj_List.position.line, 6); assert.strictEqual(Obj_List.keywords.includes(`EXPORT`), true); assert.strictEqual(Obj_List.keyword[`EXPORT`], true); @@ -518,6 +523,8 @@ exports.fixed11 = async () => { const F4DATE = cache.find(`F4DATE`); assert.strictEqual(F4DATE.subItems.length, 4); + assert.strictEqual(F4DATE.range.start, 0); + assert.strictEqual(F4DATE.range.end, 4); const parm1 = F4DATE.subItems[0]; assert.strictEqual(parm1.keywords[0], `CHAR(1)`); @@ -532,6 +539,11 @@ exports.fixed11 = async () => { const parm4 = F4DATE.subItems[3]; assert.strictEqual(parm4.keywords[0], `CHAR(1)`); assert.strictEqual(parm4.keywords[1], `OPTIONS(*NOPASS)`); + + const F4DATEDS = cache.find(`F4DATEDS`); + assert.strictEqual(F4DATEDS.subItems.length, 4); + assert.strictEqual(F4DATEDS.range.start, 6); + assert.strictEqual(F4DATEDS.range.end, 10); }; exports.columnFix = async () => { @@ -556,6 +568,24 @@ exports.columnFix = async () => { const cache = await parser.getDocs(uri, lines); assert.strictEqual(cache.procedures.length, 3); + + const abcd1 = cache.find(`abcd1`); + assert.deepStrictEqual(abcd1.range, { + start: 0, + end: 4 + }); + + const abcd2 = cache.find(`abcd2`); + assert.deepStrictEqual(abcd2.range, { + start: 5, + end: 10 + }); + + const abcd3 = cache.find(`abcd3`); + assert.deepStrictEqual(abcd3.range, { + start: 11, + end: 13 + }); }; exports.comments1 = async () => { @@ -602,19 +632,43 @@ exports.comments1 = async () => { const SndMsgPgmQ = cache.find(`SndMsgPgmQ`); assert.strictEqual(SndMsgPgmQ.subItems.length, 4); + assert.deepStrictEqual(SndMsgPgmQ.range, { + start: 2, + end: 7 + }); const ClrMsgPgmQ = cache.find(`ClrMsgPgmQ`); assert.strictEqual(ClrMsgPgmQ.subItems.length, 1); + assert.deepStrictEqual(ClrMsgPgmQ.range, { + start: 9, + end: 10 + }); const SndEscMsg = cache.find(`SndEscMsg`); assert.strictEqual(SndEscMsg.subItems.length, 1); + assert.deepStrictEqual(SndEscMsg.range, { + start: 13, + end: 14 + }); const SndInfMsg = cache.find(`SndInfMsg`); assert.strictEqual(SndInfMsg.subItems.length, 1); + assert.deepStrictEqual(SndInfMsg.range, { + start: 17, + end: 18 + }); const JobLogMsg = cache.find(`JobLogMsg`); assert.strictEqual(JobLogMsg.subItems.length, 1); + assert.deepStrictEqual(JobLogMsg.range, { + start: 21, + end: 22 + }); const Show = cache.find(`Show`); assert.strictEqual(Show.subItems.length, 3); + assert.deepStrictEqual(Show.range, { + start: 25, + end: 28 + }); }; \ No newline at end of file