Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion server/src/language/models/cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ export default class Cache {
/** @type {Keywords} */
this.keyword = {};

/** @type {Declaration[]} */
this.parameters = cache.parameters || [];

/** @type {Declaration[]} */
this.subroutines = cache.subroutines || [];

Expand Down Expand Up @@ -48,6 +51,7 @@ export default class Cache {
merge(second) {
if (second) {
return new Cache({
parameters: [...this.parameters, ...second.parameters],
subroutines: [...this.subroutines, ...second.subroutines],
procedures: [...this.procedures, ...second.procedures],
variables: [...this.variables, ...second.variables],
Expand All @@ -67,6 +71,7 @@ export default class Cache {
getNames() {
const fileStructNames = this.files.map(file => file.subItems.map(sub => sub.name)).flat();
return [
...this.parameters.map(def => def.name),
...this.constants.map(def => def.name),
...this.procedures.map(def => def.name),
...this.files.map(def => def.name),
Expand Down Expand Up @@ -107,6 +112,7 @@ export default class Cache {
const allStructs = [...fileStructs, ...this.structs];

const possibles = [
...this.parameters.filter(def => def.name.toUpperCase() === name),
...this.constants.filter(def => def.name.toUpperCase() === name),
...this.procedures.filter(def => def.name.toUpperCase() === name),
...this.files.filter(def => def.name.toUpperCase() === name),
Expand All @@ -130,7 +136,7 @@ export default class Cache {
}

clearReferences() {
[...this.constants, ...this.files, ...this.procedures, ...this.subroutines, ...this.variables, ...this.structs].forEach(def => {
[...this.parameters, ...this.constants, ...this.files, ...this.procedures, ...this.subroutines, ...this.variables, ...this.structs].forEach(def => {
def.references = [];
});

Expand Down Expand Up @@ -166,4 +172,18 @@ export default class Cache {
return globalDef;
}
}

/**
* Move all procedure subItems (the paramaters) into the cache
*/
fixProcedures() {
if (this.procedures.length > 0) {
this.procedures.forEach(proc => {
if (proc.scope && proc.subItems.length > 0) {
proc.scope.parameters = [...proc.subItems];
proc.scope.fixProcedures();
}
})
}
}
}
4 changes: 2 additions & 2 deletions server/src/language/parser.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
/* eslint-disable no-case-declarations */

import { parse } from "path";

import { createBlocks, parseStatement } from "./statement";

import Cache from "./models/cache";
Expand Down Expand Up @@ -1211,6 +1209,8 @@ export default class Parser {
scopes[0].keyword = Parser.expandKeywords(keywords);
}

scopes[0].fixProcedures();

const parsedData = scopes[0];

this.parsedCache[workingUri] = parsedData;
Expand Down
49 changes: 24 additions & 25 deletions server/src/providers/completionItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ export default async function completionItemProvider(handler: CompletionParams):
const isFree = (document.getText(Range.create(0, 0, 0, 6)).toUpperCase() === `**FREE`);

// If they're typing inside of a procedure, let's get the stuff from there too
const currentProcedure = doc.procedures.find(proc => lineNumber >= proc.range.start && lineNumber <= proc.range.end);
const currentProcedure = doc.procedures.find((proc, index) =>
lineNumber >= proc.range.start &&
(lineNumber <= proc.range.end+1 || index === doc.procedures.length-1)
);

const currentLine = document.getText(Range.create(
handler.position.line,
Expand All @@ -52,21 +55,19 @@ export default async function completionItemProvider(handler: CompletionParams):
}
}

// Ok, we have a 'preWord' (the name of the struct?)
if (preWord) {
let possibleStruct: Declaration | undefined;

if (currentProcedure && currentProcedure.scope) {
const procScop = currentProcedure.scope;

possibleStruct = currentProcedure.subItems.find(subitem => subitem.name.toUpperCase() === preWord && subitem.subItems.length > 0);

if (!possibleStruct) {
possibleStruct = procScop.structs.find(struct => struct.name.toUpperCase() === preWord);
}
}

if (!possibleStruct) {
possibleStruct = doc.structs.find(struct => struct.name.toUpperCase() === preWord);
const procScope = currentProcedure.scope;

// Look at the parms or existing structs to find a possible reference
possibleStruct = [
procScope.parameters.find(parm => parm.name.toUpperCase() === preWord && parm.subItems.length > 0),
procScope.structs.find(struct => struct.name.toUpperCase() === preWord),
doc.structs.find(struct => struct.name.toUpperCase() === preWord)
].find(x => x); // find the first non-undefined item
}

if (possibleStruct && possibleStruct.keyword[`QUALIFIED`]) {
Expand Down Expand Up @@ -101,6 +102,15 @@ export default async function completionItemProvider(handler: CompletionParams):

} else {
const expandScope = (localCache: Cache) => {
for (const subItem of localCache.parameters) {
const item = CompletionItem.create(`${subItem.name}`);
item.kind = CompletionItemKind.Variable;
item.insertText = subItem.name;
item.detail = [`parameter`, ...subItem.keywords].join(` `);
item.documentation = subItem.description;
items.push(item);
}

for (const procedure of localCache.procedures) {
const item = CompletionItem.create(`${procedure.name}`);
item.kind = CompletionItemKind.Function;
Expand Down Expand Up @@ -189,19 +199,8 @@ export default async function completionItemProvider(handler: CompletionParams):

expandScope(doc);

if (currentProcedure) {
for (const subItem of currentProcedure.subItems) {
const item = CompletionItem.create(`${subItem.name}`);
item.kind = CompletionItemKind.Variable;
item.insertText = subItem.name;
item.detail = [`parameter`, ...subItem.keywords].join(` `);
item.documentation = subItem.description;
items.push(item);
}

if (currentProcedure.scope) {
expandScope(currentProcedure.scope);
}
if (currentProcedure && currentProcedure.scope) {
expandScope(currentProcedure.scope);
}

// Next, we're going to make some import suggestions for system APIs
Expand Down
6 changes: 3 additions & 3 deletions server/src/providers/documentSymbols.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ export default async function documentSymbolProvider(handler: DocumentSymbolPara
Range.create(proc.range.start, 0, proc.range.start, 0),
);

procDef.children = proc.subItems
if (proc.scope) {
procDef.children = proc.subItems
.filter(subitem => subitem.position && subitem.position.path === currentPath)
.map(subitem => DocumentSymbol.create(
subitem.name,
Expand All @@ -37,8 +38,7 @@ export default async function documentSymbolProvider(handler: DocumentSymbolPara
Range.create(subitem.position.line, 0, subitem.position.line, 0),
Range.create(subitem.position.line, 0, subitem.position.line, 0)
));

if (proc.scope && procDef.children) {

procDef.children.push(...getScopeVars(proc.scope));
}

Expand Down
91 changes: 90 additions & 1 deletion tests/suite/linter.js
Original file line number Diff line number Diff line change
Expand Up @@ -3376,4 +3376,93 @@ exports.prettyCommentsChange = async () => {
new Position(10, 2),
),
});
};
};

exports.issue_204 = async () => {
const lines = [
`**free`,
``,
`ctl-opt dftactgrp(*no);`,
``,
`///`,
`// printf`,
`// Print to standard out`,
`// @param String value pointer`,
`///`,
`dcl-pr printf int(10) extproc('printf');`,
` format pointer value options(*string);`,
`end-pr;`,
``,
`dcl-ds person_t qualified template;`,
` name int(10);`,
` age char(50);`,
`end-ds;`,
``,
`dcl-ds myperson likeds(person_t);`,
``,
`myperson = PERSON_New();`,
`myperson.name = 'Liam Barry';`,
`myperson.age = 25;`,
`PERSON_printNice(myperson);`,
``,
`return;`,
``,
`dcl-proc PERSON_New;`,
` dcl-pi *n LikeDS(person_t) end-pi;`,
` // This is the constructor`,
` // Maybe parameters to set the defaults?`,
``,
` dcl-ds person likeds(person_t);`,
``,
` // Set defaults`,
` person.name = '';`,
` person.age = 0;`,
``,
` return person;`,
`end-proc;`,
``,
`dcl-proc PERSON_printNice;`,
` dcl-pi *n;`,
` person likeds(person_t);`,
` end-pi;`,
``,
` printf(%trim(person.name) + ' ' + %char(person.age));`,
`end-proc;`,
].join(`\n`);

const parser = parserSetup();
const cache = await parser.getDocs(uri, lines);
Linter.getErrors({ uri, content: lines }, {
CollectReferences: true,
}, cache);

// Global checks

const printf = cache.find(`printf`);
assert.strictEqual(printf.references.length, 1);

const person_t = cache.find(`person_t`);
assert.strictEqual(person_t.references.length, 4);

const myperson = cache.find(`myperson`);
assert.strictEqual(myperson.references.length, 4);

const global_person = cache.find(`person`);
assert.strictEqual(global_person, null);

// Proc A checks

const PERSON_New = cache.find(`PERSON_New`);
assert.strictEqual(PERSON_New.references.length, 1);
const PERSON_New_person = PERSON_New.scope.find(`person`);
assert.strictEqual(PERSON_New_person.references.length, 3);
assert.strictEqual(PERSON_New_person.subItems.length, 2);

// Proc B checks

const PERSON_printNice = cache.find(`PERSON_printNice`);
assert.strictEqual(PERSON_printNice.references.length, 1);
const printNice_person = PERSON_printNice.scope.find(`person`);
assert.strictEqual(printNice_person.references.length, 2);
assert.strictEqual(printNice_person.subItems.length, 2);
}