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
27 changes: 19 additions & 8 deletions cli/src/targets/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ export interface ParserError {

export type ParserErrorCallback = (error: ParserError) => void;

export interface ILEFunction {
name: string;
export?: boolean;
lineRange: [number, number];
}

export interface ILEObject {
systemName: string;
longName?: string;
Expand All @@ -36,7 +42,7 @@ export interface ILEObject {
reference?: boolean;

/** exported functions */
exports?: string[];
functions?: ILEFunction[];
/** each function import in the object */
imports?: string[];

Expand Down Expand Up @@ -483,15 +489,20 @@ export class Targets {
const allSrvPgms = this.getTargetsOfType(`SRVPGM`);
const allModules = this.getTargetsOfType(`MODULE`);

const exportsOf = (theObject: ILEObject) => {
return theObject.functions?.filter(f => f.export).map(f => f.name.toUpperCase()) || [];
}

for (const target of allSrvPgms) {
if (target.exports) {
const targetExports = exportsOf(target);
if (targetExports.length > 0) {
infoOut(`Resolving modules for ${target.systemName}.${target.type}`);

target.deps = [];

for (const exportName of target.exports) {
for (const exportName of targetExports) {
// We loop through each export of the service program and find the module that exports it
const foundModule = allModules.find(mod => mod.exports && mod.exports.includes(exportName.toUpperCase()));
const foundModule = allModules.find(mod => mod.functions && exportsOf(mod).includes(exportName.toUpperCase()));
if (foundModule) {
const alreadyBound = target.deps.some(dep => dep.systemName === foundModule.systemName && dep.type === `MODULE`);
if (!alreadyBound) {
Expand All @@ -506,7 +517,7 @@ export class Targets {
this.createOrAppend(this.projectBindingDirectory, target);

// Make sure we can resolve to this service program
for (const e of target.exports) {
for (const e of targetExports) {
this.resolvedExports[e.toUpperCase()] = target;
}
} else {
Expand Down Expand Up @@ -534,7 +545,7 @@ export class Targets {
currentTarget.deps = currentTarget.deps.filter(d => ![`SRVPGM`].includes(d.type));

for (const importName of currentTarget.imports) {
if (currentTarget.exports?.includes(importName.toUpperCase())) {
if (exportsOf(currentTarget).includes(importName.toUpperCase())) {
// This happens when a source copy has the prototype and the implementation (export)
continue; // Don't add imports that are also exports
}
Expand All @@ -551,7 +562,7 @@ export class Targets {
} else if ([`PGM`, `MODULE`].includes(currentTarget.type)) {
// Perhaps we're looking at a program object, which actually should be a multi
// module program, so we do a lookup for additional modules.
const possibleModuleDep = allModules.find(mod => mod.exports && mod.exports.includes(importName.toUpperCase()));
const possibleModuleDep = allModules.find(mod => mod.functions && exportsOf(mod).includes(importName.toUpperCase()));
if (possibleModuleDep) {
if (!newImports.some(i => i.systemName === possibleModuleDep.systemName && i.type === possibleModuleDep.type)) {
newImports.push(possibleModuleDep);
Expand Down Expand Up @@ -639,7 +650,7 @@ export class Targets {
const newModule: ILEObject = {
systemName: currentTarget.systemName,
imports: currentTarget.imports,
exports: [],
functions: [],
headers: currentTarget.headers,
type: `MODULE`,
relativePath: basePath,
Expand Down
21 changes: 17 additions & 4 deletions cli/src/targets/languages/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export async function binderTargetCallback(targets: Targets, localPath: string,
const target: ILEObjectTarget = {
...ileObject,
deps: [],
exports: []
functions: []
};

if (ileObject.extension === `binder`) {
Expand Down Expand Up @@ -58,10 +58,20 @@ export async function binderTargetCallback(targets: Targets, localPath: string,
const symbolTokens = parms[`SYMBOL`];

if (symbolTokens.block && symbolTokens.block.length === 1 && symbolTokens.block[0].type === `string` && symbolTokens.block[0].value) {
target.exports.push(trimQuotes(symbolTokens.block[0].value));
// target.exports.push(trimQuotes(symbolTokens.block[0].value));
target.functions.push({
name: trimQuotes(symbolTokens.block[0].value),
export: true,
lineRange: [0, 0] //TODO: how to get line range?
});
} else
if (symbolTokens.block && symbolTokens.block.length === 1 && symbolTokens.block[0].type === `word` && symbolTokens.block[0].value) {
target.exports.push(trimQuotes(symbolTokens.block[0].value, `"`));
// target.exports.push(trimQuotes(symbolTokens.block[0].value, `"`));
target.functions.push({
name: trimQuotes(symbolTokens.block[0].value, `"`),
export: true,
lineRange: [0, 0] // TODO: how to get line range?
});
} else {
targets.logger.fileLog(ileObject.relativePath, {
message: `Invalid EXPORT found. Single quote string expected.`,
Expand All @@ -81,7 +91,10 @@ export async function binderTargetCallback(targets: Targets, localPath: string,
}

// Exports are always uppercase
target.exports = target.exports.map(e => e.toUpperCase());
target.functions = target.functions.map(e => ({
...e,
name: e.name.toUpperCase()
}));

targets.addNewTarget(target);
}
11 changes: 6 additions & 5 deletions cli/src/targets/languages/rpgle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,14 @@ export async function rpgleTargetCallback(targets: Targets, localPath: string, c
// define exported functions
if (cache.keyword[`NOMAIN`]) {
ileObject.type = `MODULE`;

// Note that we store exports as uppercase.
ileObject.exports = cache.procedures
.filter((proc: any) => proc.keyword[`EXPORT`])
.map(ref => ref.name.toUpperCase());
}

ileObject.functions = cache.procedures.map(p => ({
name: p.name.toUpperCase(),
export: p.keyword[`EXPORT`] ? true : false,
lineRange: [p.range.start, p.range.end]
}));

infoOut(`${ileObject.systemName}.${ileObject.type}: ${ileObject.relativePath}`);

if (cache.includes && cache.includes.length > 0) {
Expand Down
11 changes: 9 additions & 2 deletions cli/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,19 +142,26 @@ export function getReferenceObjectsFrom(content: string) {

const newLine = content.includes(`\r\n`) ? `\r\n` : `\n`;

let lineNumber = -1;
const lines = content.split(newLine);

let currentObject: ILEObject;

for (let line of lines) {
lineNumber++;

const upperLine = line.trim().toUpperCase();
if (upperLine.length === 0) continue;
if (upperLine.startsWith(`#`)) continue;

// If the line starts with space, then it's an export of the parent
if (line.startsWith(` `) || line.startsWith(`\t`)) {
if (currentObject) {
currentObject.exports.push(upperLine);
currentObject.functions.push({
name: upperLine,
export: true,
lineRange: [lineNumber, lineNumber]
});
}

} else {
Expand All @@ -167,7 +174,7 @@ export function getReferenceObjectsFrom(content: string) {
currentObject = {
systemName: objectParts[0],
type: objectParts[1] as ObjectType, //TODO: validate type
exports: [],
functions: [],
reference: true,
}

Expand Down
1 change: 0 additions & 1 deletion cli/test/cl.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ describe(`CL parsing / generating`, () => {
expect(command).toBe('CRTSQLRPGI');
expect(Object.keys(parameters)).toHaveLength(8);

console.log(parameters);
expect(parameters[`compileopt`]).toBe(`'TGTCCSID(*JOB)'`);

const generated = toCl(command, parameters);
Expand Down
1 change: 0 additions & 1 deletion cli/test/cldclf.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ describe(`CL with DCLF`, () => {
expect(apgm).toBeDefined();

const logs = targets.logger.getLogsFor(apgm.relativePath);
console.log(logs);
expect(logs.length).toBe(0);

expect(apgm.deps.length).toBe(1);
Expand Down
10 changes: 0 additions & 10 deletions cli/test/cs_with_bnddir.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,13 +105,11 @@ describe(`pseudo tests`, () => {

const nept = resolvedObjects.find(f => f.systemName === `NEMP` && f.type === `FILE`);
const targetsOut = makefile.generateTargets([nept]).join(`\n`);
console.log(targetsOut);

expect(targetsOut).toContain(`all: .logs .evfevent library $(PREPATH)/NEMP.FILE`);
expect(targetsOut).not.toContain(`$(PREPATH)/NEWEMP.PGM:`);

const rules = makefile.generateGenericRules([nept]).join(`\n`);
console.log(rules);

expect(rules).toContain(`$(PREPATH)/NEMP.FILE:`);
});
Expand All @@ -125,13 +123,11 @@ describe(`pseudo tests`, () => {

const nept = resolvedObjects.find(f => f.systemName === `NEWEMP` && f.type === `PGM`);
const targetsOut = makefile.generateTargets([nept]).join(`\n`);
console.log(targetsOut);

expect(targetsOut).toContain(`all: .logs .evfevent library $(PREPATH)/NEWEMP.PGM`);
expect(targetsOut).toContain(`$(PREPATH)/NEWEMP.PGM:`);

const rules = makefile.generateGenericRules([nept]).join(`\n`);
console.log(rules);

expect(rules).toContain(`$(PREPATH)/NEMP.FILE:`);
});
Expand All @@ -145,13 +141,11 @@ describe(`pseudo tests`, () => {

const nept = resolvedObjects.find(f => f.systemName === `NEWEMP` && f.type === `PGM`);
const targetsOut = makefile.generateTargets([nept]).join(`\n`);
console.log(targetsOut);

expect(targetsOut).toContain(`all: .logs .evfevent library $(PREPATH)/NEWEMP.PGM`);
expect(targetsOut).not.toContain(`$(PREPATH)/NEWEMP.PGM:`);

const rules = makefile.generateGenericRules([nept]).join(`\n`);
console.log(rules);

expect(rules).not.toContain(`$(PREPATH)/NEMP.FILE:`);
expect(rules).toContain(`$(PREPATH)/NEWEMP.PGM: qrpglesrc/newemp.pgm.sqlrpgle`);
Expand All @@ -166,13 +160,11 @@ describe(`pseudo tests`, () => {

const nept = resolvedObjects.find(f => f.systemName === `NEMP` && f.type === `FILE`);
const targetsOut = makefile.generateTargets([nept]).join(`\n`);
console.log(targetsOut);

expect(targetsOut).toContain(`all: .logs .evfevent library $(PREPATH)/NEMP.FILE $(PREPATH)/NEWEMP.PGM $(PREPATH)/DEPTS.PGM`);
expect(targetsOut).not.toContain(`$(PREPATH)/NEWEMP.PGM:`);

const rules = makefile.generateGenericRules([nept]).join(`\n`);
console.log(rules);

expect(rules).toContain(`$(PREPATH)/NEMP.FILE:`);
expect(rules).toContain(`$(PREPATH)/NEWEMP.PGM:`);
Expand All @@ -189,13 +181,11 @@ describe(`pseudo tests`, () => {

const nept = resolvedObjects.find(f => f.systemName === `NEMP` && f.type === `FILE`);
const targetsOut = makefile.generateTargets([nept]).join(`\n`);
console.log(targetsOut);

expect(targetsOut).toContain(`all: .logs .evfevent library $(PREPATH)/NEMP.FILE $(PREPATH)/NEWEMP.PGM $(PREPATH)/DEPTS.PGM`);
expect(targetsOut).toContain(`$(PREPATH)/NEWEMP.PGM: $(PREPATH)/EMPLOYEE.FILE $(PREPATH)/NEMP.FILE`);

const rules = makefile.generateGenericRules([nept]).join(`\n`);
console.log(rules);

expect(rules).toContain(`$(PREPATH)/NEMP.FILE:`);
expect(rules).toContain(`$(PREPATH)/NEWEMP.PGM:`);
Expand Down
10 changes: 9 additions & 1 deletion cli/test/environment.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,15 @@ describe(`Reference files`, () => {
expect(refObjects[1]).toMatchObject({
type: `SRVPGM`,
systemName: `UTILS`,
exports: [`TOUPPER`, `TOLOWER`]
functions: [{
name: `TOUPPER`,
export: true,
lineRange: [6, 6]
}, {
name: `TOLOWER`,
export: true,
lineRange: [7, 7]
}],
});
});
})
45 changes: 34 additions & 11 deletions cli/test/fixtures/targets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,19 @@ export async function baseTargets(withDeps = false) {
expect(moduleA.type).toBe(`MODULE`);
expect(moduleA.extension).toBe(`rpgle`);
expect(moduleA.relativePath).toBe(path.join(`qrpglesrc`, `moduleA.rpgle`));
moduleA.exports = [`SUMNUMS`];
moduleA.functions = [
{name: `SUMNUMS`, export: true, lineRange: [0, 0]},
]

// Module MODULEB.MODULE, which exports TOLOWER
const moduleB = await targets.resolvePathToObject(path.join(cwd, `qrpglesrc`, `moduleB.sqlrpgle`));
expect(moduleB.systemName).toBe(`MODULEB`);
expect(moduleB.type).toBe(`MODULE`);
expect(moduleB.extension).toBe(`sqlrpgle`);
expect(moduleB.relativePath).toBe(path.join(`qrpglesrc`, `moduleB.sqlrpgle`));
moduleB.exports = [`TOLOWER`];
moduleB.functions = [
{name: `TOLOWER`, export: true, lineRange: [0, 0]},
];

// SRVPGMA.SRVPGM, which imports TOLOWER from MODULEB.MODULE and therefore exports TOLOWER
const srvpgmAModule = await targets.resolvePathToObject(path.join(cwd, `qsrvsrc`, `srvpgmA.bnd`));
Expand All @@ -69,7 +73,9 @@ export async function baseTargets(withDeps = false) {
expect(srvpgmAModule.extension).toBe(`bnd`);
expect(srvpgmAModule.relativePath).toBe(path.join(`qsrvsrc`, `srvpgmA.bnd`));
srvpgmAModule.imports = [`TOLOWER`];
srvpgmAModule.exports = [`TOLOWER`];
srvpgmAModule.functions = [
{name: `TOLOWER`, export: true, lineRange: [0, 0]},
];

// FILEA.FILE
const fileA = await targets.resolvePathToObject(path.join(cwd, `qddssrc`, `fileA.sql`));
Expand All @@ -87,7 +93,9 @@ export async function baseTargets(withDeps = false) {

// ORDENTSRV.SRVPGM, which exports/imports FIXTOTALS
const ORDENTSRV = await targets.resolvePathToObject(path.join(cwd, `qbndsrc`, `ordentsrv.binder`));
ORDENTSRV.exports = [`FIXTOTALS`];
ORDENTSRV.functions = [
{name: `FIXTOTALS`, export: true, lineRange: [0, 0]}
]
ORDENTSRV.imports = [`FIXTOTALS`];
expect(ORDENTSRV.systemName).toBe(`ORDENTSRV`);
expect(ORDENTSRV.type).toBe(`SRVPGM`);
Expand All @@ -96,15 +104,19 @@ export async function baseTargets(withDeps = false) {

// ORDENTMOD.MODULE which exports FIXTOTALS
const ORDENTMOD = await targets.resolvePathToObject(path.join(cwd, `qrpglesrc`, `ordentmod.rpgle`));
ORDENTMOD.exports = [`FIXTOTALS`];
ORDENTMOD.functions = [
{name: `FIXTOTALS`, export: true, lineRange: [0, 0]}
];
expect(ORDENTMOD.systemName).toBe(`ORDENTMOD`);
expect(ORDENTMOD.type).toBe(`MODULE`);
expect(ORDENTMOD.extension).toBe(`rpgle`);
expect(ORDENTMOD.relativePath).toBe(path.join(`qrpglesrc`, `ordentmod.rpgle`));

// UNUSEDSRV.SRVPGM, which exports BIGNOPE and is not used.
const UNUSEDSRV = await targets.resolvePathToObject(path.join(cwd, `qbndsrc`, `unusedsrv.binder`));
UNUSEDSRV.exports = [`BIGNOPE`];
UNUSEDSRV.functions = [
{name: `BIGNOPE`, export: true, lineRange: [0, 0]}
];
expect(UNUSEDSRV.systemName).toBe(`UNUSEDSRV`);
expect(UNUSEDSRV.type).toBe(`SRVPGM`);
expect(UNUSEDSRV.extension).toBe(`binder`);
Expand Down Expand Up @@ -138,26 +150,37 @@ export async function multiModuleObjects() {

// Module that is required by the MYWEBAPP.PGM
const handlerAMod = await targets.resolvePathToObject(path.join(cwd, `qrpglesrc`, `handlerA.rpgle`));
handlerAMod.exports = [`ROUTEHANDLERA`];
handlerAMod.functions = [
{name: `ROUTEHANDLERA`, export: true, lineRange: [0, 0]}
]
handlerAMod.imports = [`JSON_SQLRESULTSET`, `IL_RESPONSEWRITESTREAM`];

// Another module that is required by the MYWEBAPP.PGM
const handlerBMod = await targets.resolvePathToObject(path.join(cwd, `qrpglesrc`, `handlerB.rpgle`));
handlerBMod.exports = [`ROUTEHANDLERB`];
handlerBMod.functions = [
{name: `ROUTEHANDLERB`, export: true, lineRange: [0, 0]}
]
handlerBMod.imports = [`API_VALIDATE`, `JSON_SQLRESULTSET`, `IL_RESPONSEWRITESTREAM`];

// Another module that is part of the JWTHANDLER.SRVPGM object
const jwtHandlerMod = await targets.resolvePathToObject(path.join(cwd, `qrpglesrc`, `jwtHandler.rpgle`));
jwtHandlerMod.exports = [`JWT_MIDDLEWARE`];
jwtHandlerMod.functions = [
{name: `JWT_MIDDLEWARE`, export: true, lineRange: [0, 0]}
]

// Another module that is part of the JWTHANDLER.SRVPGM object
const validateMod = await targets.resolvePathToObject(path.join(cwd, `qrpglesrc`, `validate.rpgle`));
validateMod.exports = [`API_VALIDATE`];
validateMod.functions = [
{name: `API_VALIDATE`, export: true, lineRange: [0, 0]}
];

// Service program for JWTHANDLER, used by MYWEBAPP
const jwtHandlerSrv = await targets.resolvePathToObject(path.join(cwd, `qsrvsrc`, `utils.binder`));
jwtHandlerSrv.imports = [`JWT_MIDDLEWARE`, `API_VALIDATE`];
jwtHandlerSrv.exports = [`JWT_MIDDLEWARE`, `API_VALIDATE`];
jwtHandlerSrv.functions = [
{name: `JWT_MIDDLEWARE`, export: true, lineRange: [0, 0]},
{name: `API_VALIDATE`, export: true, lineRange: [0, 0]}
];

targets.createOrAppend(myWebApp);
targets.createOrAppend(handlerAMod);
Expand Down
Loading
Loading