diff --git a/cli/src/targets.ts b/cli/src/targets.ts index 8bb4858..eaf31b6 100644 --- a/cli/src/targets.ts +++ b/cli/src/targets.ts @@ -483,7 +483,6 @@ export class Targets { * Handles all DDS types: pf, lf, dspf */ private createDdsFileTarget(localPath: string, dds: dds, options: FileOptions = {}) { - const sourceName = path.basename(localPath); const ileObject = this.resolvePathToObject(localPath, options.text); const target: ILEObjectTarget = { ...ileObject, @@ -492,47 +491,72 @@ export class Targets { infoOut(`${ileObject.systemName}.${ileObject.type}: ${ileObject.relativePath}`); + // We have a local cache of refs found so we don't keep doing global lookups + // on objects we already know to depend on in this object. + + let alreadyFoundRefs: string[] = []; + + const handleObjectPath = (currentKeyword: string, recordFormat: any, value: string) => { + const qualified = value.split(`/`); + + let objectName: string | undefined; + if (qualified.length === 2 && qualified[0].toLowerCase() === `*libl`) { + objectName = qualified[1]; + } else if (qualified.length === 1) { + objectName = qualified[0]; + } + + if (objectName) { + const upperName = objectName.toUpperCase(); + if (alreadyFoundRefs.includes(upperName)) return; + + const resolvedPath = this.searchForObject({ systemName: upperName, type: `FILE` }); + if (resolvedPath) { + target.deps.push(resolvedPath); + alreadyFoundRefs.push(upperName); + } + else { + this.logger.fileLog(ileObject.relativePath, { + message: `no object found for reference '${objectName}'`, + type: `warning`, + line: recordFormat.range.start + }); + } + } else { + this.logger.fileLog(ileObject.relativePath, { + message: `${currentKeyword} reference not included as possible reference to library found.`, + type: `info`, + line: recordFormat.range.start + }); + } + } + + // PFILE -> https://www.ibm.com/docs/en/i/7.5?topic=80-pfile-physical-file-keywordlogical-files-only + // REF -> https://www.ibm.com/docs/en/i/7.5?topic=80-ref-reference-keywordphysical-files-only + const ddsRefKeywords = [`PFILE`, `REF`, `JFILE`]; for (const recordFormat of dds.formats) { + // Look through this record format keywords for the keyword we're looking for for (const keyword of ddsRefKeywords) { - // Look through this record format keywords for the keyword we're looking for const keywordObj = recordFormat.keywords.find(k => k.name === keyword); if (keywordObj) { const wholeValue: string = keywordObj.value; const parts = wholeValue.split(` `).filter(x => x.length > 0); - for (const value of parts) { - const qualified = value.split(`/`); - - let objectName: string | undefined; - if (qualified.length === 2 && qualified[0].toLowerCase() === `*libl`) { - objectName = qualified[1]; - } else if (qualified.length === 1) { - objectName = qualified[0]; - } - if (objectName) { - const resolvedPath = this.searchForObject({ systemName: objectName.toUpperCase(), type: `FILE` }); - if (resolvedPath) target.deps.push(resolvedPath); - else { - this.logger.fileLog(ileObject.relativePath, { - message: `no object found for reference '${objectName}'`, - type: `warning`, - line: recordFormat.range.start - }); - } - } else { - this.logger.fileLog(ileObject.relativePath, { - message: `${keyword} reference not included as possible reference to library found.`, - type: `info`, - line: recordFormat.range.start - }); - } + // JFILE can have multiple files referenced in it, whereas + // REF and PFILE can only have one at the first element + const pathsToCheck = (keyword === `JFILE` ? parts.length : 1); + + for (let i = 0; i < pathsToCheck; i++) { + handleObjectPath(keyword, recordFormat, parts[i]); } } } + // REFFLD -> https://www.ibm.com/docs/en/i/7.5?topic=80-reffld-referenced-field-keywordphysical-files-only + // Then, let's loop through the fields in this format and see if we can find REFFLD for (const field of recordFormat.fields) { const refFld = field.keywords.find(k => k.name === `REFFLD`); @@ -541,36 +565,7 @@ export class Targets { const [fieldRef, fileRef] = refFld.value.trim().split(` `); if (fileRef) { - const qualified = fileRef.split(`/`); - - let objectName: string | undefined; - if (qualified.length === 2 && qualified[0].toLowerCase() === `*libl`) { - objectName = qualified[1]; - } else if (qualified.length === 1) { - objectName = qualified[0]; - } - - if (objectName) { - const resolvedPath = this.searchForObject({ systemName: objectName.toUpperCase(), type: `FILE` }); - if (resolvedPath) { - if (!target.deps.some(d => d.systemName === resolvedPath.systemName && d.type === resolvedPath.type)) { - target.deps.push(resolvedPath); - } - } - else { - this.logger.fileLog(ileObject.relativePath, { - message: `no object found for reference '${objectName}'`, - type: `warning`, - line: recordFormat.range.start - }); - } - } else { - this.logger.fileLog(ileObject.relativePath, { - message: `REFFLD reference not included as possible reference to library found.`, - type: `info`, - line: recordFormat.range.start - }); - } + handleObjectPath(`REFFLD`, recordFormat, fileRef); } } } diff --git a/cli/test/ddsReferences.test.ts b/cli/test/ddsReferences.test.ts new file mode 100644 index 0000000..1112bba --- /dev/null +++ b/cli/test/ddsReferences.test.ts @@ -0,0 +1,86 @@ +import { assert, beforeAll, describe, expect, test } from 'vitest'; + +import { Targets } from '../src/targets' +import path from 'path'; +import { MakeProject } from '../src/builders/make'; +import { getFiles } from '../src/utils'; +import { setupFixture } from './fixtures/projects'; +import { scanGlob } from '../src/extensions'; +import { writeFileSync } from 'fs'; + +const cwd = setupFixture(`dds_refs`); + +// This issue was occuring when you had two files with the same name, but different extensions. + +let files = getFiles(cwd, scanGlob); + +describe.skipIf(files.length === 0)(`dds_refs tests`, () => { + const targets = new Targets(cwd); + targets.setSuggestions({renames: true, includes: true}) + + beforeAll(async () => { + targets.loadObjectsFromPaths(files); + const parsePromises = files.map(f => targets.parseFile(f)); + await Promise.all(parsePromises); + + expect(targets.getTargets().length).toBeGreaterThan(0); + targets.resolveBinder(); + }); + + test(`Ensure objects are defined`, async () => { + expect(targets.getTargets().length).toBe(3); + expect(targets.getResolvedObjects(`FILE`).length).toBe(3); + expect(targets.binderRequired()).toBe(false); + + const pro250d = targets.searchForObject({systemName: `PRO250D`, type: `FILE`}); + expect(pro250d).toBeDefined(); + const provider = targets.searchForObject({systemName: `PROVIDER`, type: `FILE`}); + expect(provider).toBeDefined(); + const provide1 = targets.searchForObject({systemName: `PROVIDE1`, type: `FILE`}); + expect(provide1).toBeDefined(); + }); + + test(`test PROD250D deps (REF & 32REFFLD)`, async () => { + const pro250d = targets.getTarget({systemName: `PRO250D`, type: `FILE`}); + + expect(pro250d).toBeDefined(); + const deps = pro250d.deps; + expect(deps.length).toBe(1); + expect(deps[0].systemName).toBe(`PROVIDER`); + + const logs = targets.logger.getLogsFor(pro250d.relativePath); + expect(logs.length).toBe(1); + expect(logs[0]).toMatchObject({ + message: `no object found for reference 'COUNTRY'`, + type: `warning`, + line: 32 + }); + }); + + test(`test PROVIDER deps (REF)`, async () => { + const provider = targets.getTarget({systemName: `PROVIDER`, type: `FILE`}); + + expect(provider).toBeDefined(); + const deps = provider.deps; + expect(deps.length).toBe(0); + + const logs = targets.logger.getLogsFor(provider.relativePath); + expect(logs.length).toBe(1); + expect(logs[0]).toMatchObject({ + message: `no object found for reference 'SAMREF'`, + type: `warning`, + line: -1 + }); + }); + + test(`test PROVIDE1 deps (REF)`, async () => { + const providerLf = targets.getTarget({systemName: `PROVIDE1`, type: `FILE`}); + + expect(providerLf).toBeDefined(); + const deps = providerLf.deps; + expect(deps.length).toBe(1); + + const logs = targets.logger.getLogsFor(providerLf.relativePath); + expect(logs).toBeUndefined(); + }); +}); \ No newline at end of file diff --git a/cli/test/fixtures/dds_refs/PRO250D.DSPF b/cli/test/fixtures/dds_refs/PRO250D.DSPF new file mode 100644 index 0000000..8b329de --- /dev/null +++ b/cli/test/fixtures/dds_refs/PRO250D.DSPF @@ -0,0 +1,67 @@ + *%METADATA * + * %TEXT Display Article * + *%EMETADATA * + A*%%TS SD 20210414 103200 VTAQUIN REL-V7R3M0 5770-WDS + A*%%EC + A DSPSIZ(24 80 *DS3) + A REF(*LIBL/PROVIDER FPROV) + A PRINT + A INDARA + A ERRSFL + A CA03(03) + A CA12(12) + A R FMT01 + A*%%TS SD 20210330 110722 VTAQUIN REL-V7R3M0 5770-WDS + A CF04(04 'Prompt') + A 1 2'PRO250 ' + A COLOR(BLU) + A 3 4'Type choices, press Enter.' + A COLOR(BLU) + A 23 3'F3=Exit' + A COLOR(BLU) + A 23 37'F12=Cancel' + A COLOR(BLU) + A 1 34'Provider by Id' + A DSPATR(HI) + A 5 4'Provider Id . . . . . .' + A 23 19'F4=Prompt' + A COLOR(BLU) + A PRID R B 5 29 + A 40 ERRMSGID(ERR0103 *LIBL/SAMMSGF 40 &- + A ERRDATA) + A ERRDATA 6A P + A R FMT02 + A*%%TS SD 20210414 103200 VTAQUIN REL-V7R3M0 5770-WDS + A CA07(07 'Items') + A 1 2'PRO250 ' + A COLOR(BLU) + A 3 4'Press Enter to continue.' + A COLOR(BLU) + A 23 3'F3=Exit' + A COLOR(BLU) + A 23 37'F12=Cancel' + A COLOR(BLU) + A 5 4'Provider Id . . . . . :' + A 6 4'Name . . . . . . . . :' + A 7 4'Phone . . . . . . . . :' + A 8 4'Vat Nr . . . . . . . :' + A 9 4'eMail . . . . . . . . :' + A 10 4'Address . . . . . . . :' + A 13 4'Postal Code & City . :' + A 14 4'Country Code . . . . :' + A 1 34'Provider by Id' + A DSPATR(HI) + A PRID R O 5 29 + A PROVNM R O 6 29 + A PRPHONE R O 7 29 + A PRVAT R O 8 29 + A PRMAIL R O 9 29 + A PRLINE1 R O 10 29 + A PRLINE2 R O 11 29 + A PRLINE3 R O 12 29 + A PRZIP R O 13 29 + A PRCOUN R O 14 29 + A PRCITY R O 13 40 + A COUNTR R O 14 32REFFLD(FCOUN/COUNTR *LIBL/COUNTRY) + A 23 18'F7=Items' + A COLOR(BLU) diff --git a/cli/test/fixtures/dds_refs/PROVIDE1.LF b/cli/test/fixtures/dds_refs/PROVIDE1.LF new file mode 100644 index 0000000..917e321 --- /dev/null +++ b/cli/test/fixtures/dds_refs/PROVIDE1.LF @@ -0,0 +1,6 @@ + *%METADATA * + * %TEXT Provider file * + *%EMETADATA * + UNIQUE + R FPROV PFILE(PROVIDER) + K PRID diff --git a/cli/test/fixtures/dds_refs/PROVIDER.PF b/cli/test/fixtures/dds_refs/PROVIDER.PF new file mode 100644 index 0000000..36f85df --- /dev/null +++ b/cli/test/fixtures/dds_refs/PROVIDER.PF @@ -0,0 +1,24 @@ + *%METADATA * + * %TEXT Provider file * + *%EMETADATA * + REF(SAMREF) + R FPROV + PRID R + PROVNM R + PRCONT 30 TEXT('CONTACT PERSON') + PRPHONE R REFFLD(PHONE) + PRVAT R REFFLD(VATNUM) + PRMAIL R REFFLD(EMAIL) + PRLINE1 R REFFLD(ADRLINE) + PRLINE2 R REFFLD(ADRLINE) + PRLINE3 R REFFLD(ADRLINE) + PRZIP R REFFLD(ZIPCOD) + PRCITY R REFFLD(CITY) + PRCOUN R REFFLD(COID) + PRCREA L TEXT('CREATION DATE') + COLHDG('CREAETION' 'DATE') + PRMOD Z TEXT('LAST MODIFICATION') + COLHDG('LAST' 'MODIFICATION') + PRMODID 10 TEXT('LAS MOD BY') + COLHDG('LAST' 'MODIF.' 'BY') + PRDEL R REFFLD(DLCODE)