diff --git a/cli/src/targets/index.ts b/cli/src/targets/index.ts index ef51095..2b52a33 100644 --- a/cli/src/targets/index.ts +++ b/cli/src/targets/index.ts @@ -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; @@ -36,7 +42,7 @@ export interface ILEObject { reference?: boolean; /** exported functions */ - exports?: string[]; + functions?: ILEFunction[]; /** each function import in the object */ imports?: string[]; @@ -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) { @@ -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 { @@ -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 } @@ -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); @@ -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, diff --git a/cli/src/targets/languages/binder.ts b/cli/src/targets/languages/binder.ts index 1ed74ed..cec3d4d 100644 --- a/cli/src/targets/languages/binder.ts +++ b/cli/src/targets/languages/binder.ts @@ -21,7 +21,7 @@ export async function binderTargetCallback(targets: Targets, localPath: string, const target: ILEObjectTarget = { ...ileObject, deps: [], - exports: [] + functions: [] }; if (ileObject.extension === `binder`) { @@ -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.`, @@ -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); } \ No newline at end of file diff --git a/cli/src/targets/languages/rpgle.ts b/cli/src/targets/languages/rpgle.ts index 9dd6617..4c18312 100644 --- a/cli/src/targets/languages/rpgle.ts +++ b/cli/src/targets/languages/rpgle.ts @@ -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) { diff --git a/cli/src/utils.ts b/cli/src/utils.ts index 08fdaba..d8993f0 100644 --- a/cli/src/utils.ts +++ b/cli/src/utils.ts @@ -142,11 +142,14 @@ 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; @@ -154,7 +157,11 @@ export function getReferenceObjectsFrom(content: string) { // 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 { @@ -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, } diff --git a/cli/test/cl.test.ts b/cli/test/cl.test.ts index 085fb73..30ae98c 100644 --- a/cli/test/cl.test.ts +++ b/cli/test/cl.test.ts @@ -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); diff --git a/cli/test/cldclf.test.ts b/cli/test/cldclf.test.ts index d6d7ff4..ddda701 100644 --- a/cli/test/cldclf.test.ts +++ b/cli/test/cldclf.test.ts @@ -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); diff --git a/cli/test/cs_with_bnddir.test.ts b/cli/test/cs_with_bnddir.test.ts index ce37ce8..084a1f6 100644 --- a/cli/test/cs_with_bnddir.test.ts +++ b/cli/test/cs_with_bnddir.test.ts @@ -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:`); }); @@ -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:`); }); @@ -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`); @@ -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:`); @@ -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:`); diff --git a/cli/test/environment.test.ts b/cli/test/environment.test.ts index b5792f4..b2e6ad1 100644 --- a/cli/test/environment.test.ts +++ b/cli/test/environment.test.ts @@ -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] + }], }); }); }) \ No newline at end of file diff --git a/cli/test/fixtures/targets.ts b/cli/test/fixtures/targets.ts index b81fb06..4f6b039 100644 --- a/cli/test/fixtures/targets.ts +++ b/cli/test/fixtures/targets.ts @@ -52,7 +52,9 @@ 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`)); @@ -60,7 +62,9 @@ export async function baseTargets(withDeps = false) { 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`)); @@ -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`)); @@ -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`); @@ -96,7 +104,9 @@ 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`); @@ -104,7 +114,9 @@ export async function baseTargets(withDeps = false) { // 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`); @@ -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); diff --git a/cli/test/make.test.ts b/cli/test/make.test.ts index 754e712..7091482 100644 --- a/cli/test/make.test.ts +++ b/cli/test/make.test.ts @@ -203,8 +203,6 @@ test('generateTargets (post-resolve)', async () => { const targetContent = project.generateTargets([srvpgma]); - console.log(targetContent.join('\n')); - expect(targetContent).toEqual( [ 'all: .logs .evfevent library $(PREPATH)/SRVPGMA.SRVPGM', @@ -223,7 +221,6 @@ test('generateTargets (post-resolve)', async () => { const rules = project.generateGenericRules([srvpgma]); - console.log(rules.join('\n')); expect(rules).toContain(`$(PREPATH)/MODULEB.MODULE: qrpglesrc/moduleB.sqlrpgle`); expect(rules).toContain(`$(PREPATH)/SRVPGMA.SRVPGM: qsrvsrc/srvpgmA.bnd`); expect(rules).toContain(`$(PREPATH)/FILEB.FILE: qddssrc/fileB.pf`); diff --git a/cli/test/mixedCaseExport.test.ts b/cli/test/mixedCaseExport.test.ts index 15d0e19..c543ae4 100644 --- a/cli/test/mixedCaseExport.test.ts +++ b/cli/test/mixedCaseExport.test.ts @@ -1,6 +1,6 @@ import { beforeAll, describe, expect, test } from 'vitest'; -import { Targets } from '../src/targets' +import { ILEObject, Targets } from '../src/targets' import { setupFixture } from './fixtures/projects'; import { ReadFileSystem } from '../src/readFileSystem'; @@ -35,8 +35,13 @@ describe(`pr with mixed case exports exports `, () => { expect(srvPgmTarget.deps.length).toBe(1); - expect(srvPgmTarget.exports.length).toBe(3); - expect(srvPgmTarget.exports).toStrictEqual(srvPgmTarget.deps[0].exports); + expect(srvPgmTarget.functions.length).toBe(3); + + const exportsOf = (theObject: ILEObject) => { + return theObject.functions.filter(f => f.export).map(f => f.name); + } + + expect(exportsOf(srvPgmTarget)).toStrictEqual(exportsOf(srvPgmTarget.deps[0])); expect(allLogs[srvPgmObj.relativePath].length).toBe(0); }); diff --git a/cli/test/project2.test.ts b/cli/test/project2.test.ts index 6874dcd..453303e 100644 --- a/cli/test/project2.test.ts +++ b/cli/test/project2.test.ts @@ -326,8 +326,6 @@ describe(`company_system tests`, () => { // Generate targets on it's own will have BNDDIR, PGM, etc const headerContent = makeProject.generateTargets([employeeFile]); - console.log(headerContent.join(`\n`)); - const allTarget = headerContent.find(l => l.startsWith(`all:`)); expect(allTarget).toBeDefined(); @@ -358,7 +356,6 @@ describe(`company_system tests`, () => { expect(allTarget).toBeDefined(); const allTargets = allTarget.substring(5).split(` `); - console.log(allTargets); expect(allTargets[0]).toBe(`.logs`); expect(allTargets[1]).toBe(`.evfevent`); expect(allTargets[2]).toBe(`library`); @@ -367,8 +364,6 @@ describe(`company_system tests`, () => { expect(allTargets).toContain(`$(PREPATH)/DEPTS.PGM`); expect(allTargets).toContain(`$(PREPATH)/SHOWEMPS.PGM`); expect(allTargets).toContain(`$(PREPATH)/GETTOTSAL.SRVPGM`); - - console.log(headerContent.join(`\n`)); }); test(`Impact of EMPLOYEES`, () => {