From 656ec61a68bc3245b1dd9690c98ced5284d93763 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Tue, 1 Apr 2025 09:43:06 -0400 Subject: [PATCH 01/18] Initial work to cleanup typing Signed-off-by: worksofliam --- cli/src/builders/bob.ts | 6 +- cli/src/builders/imd.ts | 10 +-- cli/src/builders/make/index.ts | 20 ++--- cli/src/index.ts | 2 +- cli/src/targets.ts | 128 +++++++++++++++------------- cli/test/autofix.test.ts | 2 +- cli/test/ddsDeps.test.ts | 6 +- cli/test/ddsDepsWithRefFile.test.ts | 6 +- cli/test/fixtures/targets.ts | 52 +++++------ cli/test/includeMismatchFix.test.ts | 2 +- cli/test/mixedCaseExport.test.ts | 2 +- cli/test/project.test.ts | 54 ++++++------ cli/test/project2.test.ts | 54 ++++++------ cli/test/sqlLongNames.test.ts | 2 +- cli/test/sqlReference.test.ts | 4 +- cli/test/sqlReferenceWith.test.ts | 4 +- cli/test/targets.test.ts | 23 +++-- 17 files changed, 193 insertions(+), 184 deletions(-) diff --git a/cli/src/builders/bob.ts b/cli/src/builders/bob.ts index 3e5a5f6..c6e24c7 100644 --- a/cli/src/builders/bob.ts +++ b/cli/src/builders/bob.ts @@ -15,8 +15,8 @@ export class BobProject { let list: DirectoryTargets = {}; for (let target of targets.getTargets()) { - if (target.relativePath) { - const dirname = path.dirname(target.relativePath); + if (target.source) { + const dirname = path.dirname(target.source.relativePath); if (list[dirname] === undefined) list[dirname] = []; list[dirname].push(target); @@ -85,7 +85,7 @@ class RulesFile { const existingLine = this.parsed.find(r => r.target === objName && r.isUserWritten !== true); - const lineContent = `${path.relative(this.subdir, target.relativePath)} ${target.headers ? target.headers.join(` `) + ` ` : ``}${target.deps.filter(d => d.reference !== true).map(d => `${d.systemName}.${d.type}`).join(` `)}`.trimEnd(); + const lineContent = `${path.relative(this.subdir, target.source.relativePath)} ${target.headers ? target.headers.join(` `) + ` ` : ``}${target.deps.filter(d => d.reference !== true).map(d => `${d.systemName}.${d.type}`).join(` `)}`.trimEnd(); if (existingLine) { existingLine.ogLine = `${objName}: ${lineContent}`; diff --git a/cli/src/builders/imd.ts b/cli/src/builders/imd.ts index 96d9065..08a0c6d 100644 --- a/cli/src/builders/imd.ts +++ b/cli/src/builders/imd.ts @@ -30,11 +30,11 @@ export class ImpactMarkdown { if (this.relativePaths.length > 0) { lines.push(`## Impact Analysis`, ``); - const possibleObjects = this.relativePaths.map(r => this.targets.getResolvedObject(path.join(this.cwd, r))).filter(x => x && x.relativePath); + const possibleObjects = this.relativePaths.map(r => this.targets.getResolvedObject(path.join(this.cwd, r))).filter(x => x && x.source); lines.push( `Touched objects: `, ``, - ...possibleObjects.map(ileObject => `* ${TypeEmoji[ileObject.type] || `❔`} \`${ileObject.systemName}.${ileObject.type}\`: \`${ileObject.relativePath}\``), + ...possibleObjects.map(ileObject => `* ${TypeEmoji[ileObject.type] || `❔`} \`${ileObject.systemName}.${ileObject.type}\`: \`${ileObject.source.relativePath}\``), ``, `---`, `` @@ -141,7 +141,7 @@ export class ImpactMarkdown { function lookupObject(ileObject: ILEObject) { let resultLines: string[] = []; - resultLines.push(`${''.padEnd(currentTree.length, `\t`)}* ${TypeEmoji[ileObject.type] || `❔`} \`${ileObject.systemName}.${ileObject.type}\` (${ileObject.relativePath ? `\`${ileObject.relativePath}\`` : `no source`})`); + resultLines.push(`${''.padEnd(currentTree.length, `\t`)}* ${TypeEmoji[ileObject.type] || `❔`} \`${ileObject.systemName}.${ileObject.type}\` (${ileObject.source ? `\`${ileObject.source.relativePath}\`` : `no source`})`); currentTree.push(ileObject); @@ -180,7 +180,7 @@ export class ImpactMarkdown { this.targets.getResolvedObjects().forEach(ileObject => { - let logs = this.targets.logger.getLogsFor(ileObject.relativePath) || []; + let logs = this.targets.logger.getLogsFor(ileObject.source?.relativePath) || []; let parents = this.targets.getTargets().filter(t => t.deps.some(d => d.systemName === ileObject.systemName && d.type === ileObject.type)); let children = this.targets.getTarget(ileObject)?.deps || []; @@ -188,7 +188,7 @@ export class ImpactMarkdown { TypeEmoji[ileObject.type] || `❔`, ileObject.systemName, ileObject.type, - `\`${ileObject.relativePath}\``, + `\`${ileObject.source?.relativePath}\``, ImpactMarkdown.createLogExpand(logs), ImpactMarkdown.createObjectExpand(parents), ImpactMarkdown.createObjectExpand(children) diff --git a/cli/src/builders/make/index.ts b/cli/src/builders/make/index.ts index 4981610..e4274cf 100644 --- a/cli/src/builders/make/index.ts +++ b/cli/src/builders/make/index.ts @@ -46,9 +46,9 @@ export class MakeProject { public getObjectAttributes(compileData: CompileData, ileObject: ILEObject): CommandParameters { let customAttributes = this.settings.objectAttributes[`${ileObject.systemName}.${ileObject.type}`] || {}; - if (ileObject.relativePath) { + if (ileObject.source) { // We need to take in the current folders .ibmi.json file for any specific values - const folder = path.dirname(ileObject.relativePath); + const folder = ileObject.source && ileObject.source.relativePath ? path.dirname(ileObject.source.relativePath) : undefined; const folderSettings = this.folderSettings[folder]; if (folderSettings) { // If there is a tgtccsid, we only want to apply it to commands @@ -174,8 +174,8 @@ export class MakeProject { for (const ileObject of objects) { if (ileObject.reference) continue; - if (ileObject.relativePath) { - const sourcePath = path.join(this.cwd, ileObject.relativePath); + if (ileObject.source) { + const sourcePath = path.join(this.cwd, ileObject.source.relativePath); const exists = existsSync(sourcePath); // Is this even needed? We already have relativePath?? if (exists) { @@ -187,13 +187,13 @@ export class MakeProject { const customAttributes = this.settings.objectAttributes[`${ileObject.systemName}.${ileObject.type}`]; lines.push( - `$(PREPATH)/${ileObject.systemName}.${data.becomes}: ${asPosix(ileObject.relativePath)}`, + `$(PREPATH)/${ileObject.systemName}.${data.becomes}: ${asPosix(ileObject.source.relativePath)}`, ...(commands.map(l => `\t-system -q "${toCl(l, customAttributes)}"`)), ``, ); } catch (e) { - console.log(`Failed to parse '${ileObject.relativePath}'`); + console.log(`Failed to parse '${ileObject.source.relativePath}'`); process.exit(); } } @@ -245,13 +245,13 @@ export class MakeProject { static generateSpecificTarget(data: CompileData, ileObject: ILEObjectTarget, customAttributes?: CommandParameters): string[] { let lines: string[] = []; - const parentName = ileObject.relativePath ? path.dirname(ileObject.relativePath) : undefined; + const parentName = ileObject.source?.relativePath ? path.dirname(ileObject.source.relativePath) : undefined; const qsysTempName: string | undefined = (parentName && parentName.length > 10 ? parentName.substring(0, 10) : parentName); const resolve = (command: string) => { command = command.replace(new RegExp(`\\*CURLIB`, `g`), `$(BIN_LIB)`); command = command.replace(new RegExp(`\\$\\*`, `g`), ileObject.systemName); - command = command.replace(new RegExp(`\\$<`, `g`), asPosix(ileObject.relativePath)); + command = command.replace(new RegExp(`\\$<`, `g`), asPosix(ileObject.source.relativePath)); command = command.replace(new RegExp(`\\$\\(SRCPF\\)`, `g`), qsysTempName); if (ileObject.deps && ileObject.deps.length > 0) { @@ -287,12 +287,12 @@ export class MakeProject { const resolvedCommand = resolve(toCl(data.command, data.parameters)); lines.push( - `$(PREPATH)/${objectKey}: ${asPosix(ileObject.relativePath)}`, + `$(PREPATH)/${objectKey}: ${asPosix(ileObject.source.relativePath)}`, ...(qsysTempName && data.member ? [ // TODO: consider CCSID when creating the source file `\t-system -qi "CRTSRCPF FILE($(BIN_LIB)/${qsysTempName}) RCDLEN(112) CCSID(${sourceFileCcsid})"`, - `\tsystem "CPYFRMSTMF FROMSTMF('${asPosix(ileObject.relativePath)}') TOMBR('$(PREPATH)/${qsysTempName}.FILE/${ileObject.systemName}.MBR') MBROPT(*REPLACE)"` + `\tsystem "CPYFRMSTMF FROMSTMF('${asPosix(ileObject.source.relativePath)}') TOMBR('$(PREPATH)/${qsysTempName}.FILE/${ileObject.systemName}.MBR') MBROPT(*REPLACE)"` ] : []), ...(data.preCommands ? data.preCommands.map(cmd => `\t${resolve(cmd)}`) : []), ...(data.command ? diff --git a/cli/src/index.ts b/cli/src/index.ts index 8b7aec4..f940e07 100644 --- a/cli/src/index.ts +++ b/cli/src/index.ts @@ -297,7 +297,7 @@ async function listDeps(cwd: string, targets: Targets, query: string) { let currentTree: ILEObject[] = []; function lookupObject(ileObject: ILEObject) { - console.log(`${''.padEnd(currentTree.length, `\t`)}${ileObject.systemName}.${ileObject.type} (${ileObject.relativePath || `no source`})`); + console.log(`${''.padEnd(currentTree.length, `\t`)}${ileObject.systemName}.${ileObject.type} (${ileObject.source ? ileObject.source.relativePath : `no source`})`); currentTree.push(ileObject); diff --git a/cli/src/targets.ts b/cli/src/targets.ts index ef103ad..6d255e3 100644 --- a/cli/src/targets.ts +++ b/cli/src/targets.ts @@ -39,8 +39,10 @@ export interface ILEObject { longName?: string; type: ObjectType; text?: string, - relativePath?: string; - extension?: string; + source?: { + relativePath: string; + extension: string; + } reference?: boolean; @@ -166,8 +168,10 @@ export class Targets { systemName: name, type: type, text: newText, - relativePath, - extension + source: { + relativePath, + extension + } }; // If this file is an SQL file, we need to look to see if it has a long name as we need to resolve all names here @@ -244,14 +248,14 @@ export class Targets { const target = this.targets[targetId]; if (target) { - const depIndex = target.deps.findIndex(d => (d.systemName === resolvedObject.systemName && d.type === resolvedObject.type) || d.relativePath === resolvedObject.relativePath); + const depIndex = target.deps.findIndex(d => (d.systemName === resolvedObject.systemName && d.type === resolvedObject.type) || d.source.relativePath === resolvedObject.source.relativePath); if (depIndex >= 0) { impactedTargets.push(target); target.deps.splice(depIndex, 1); - if (target.relativePath) { - this.logger.fileLog(target.relativePath, { + if (target.source) { + this.logger.fileLog(target.source.relativePath, { type: `info`, message: `This object depended on ${resolvedObject.systemName}.${resolvedObject.type} before it was deleted.` }) @@ -265,8 +269,8 @@ export class Targets { this.resolvedSearches[`${resolvedObject.systemName}.${resolvedObject.type}`] = undefined; // Remove possible logs - if (resolvedObject.relativePath) { - this.logger.flush(resolvedObject.relativePath) + if (resolvedObject.source.relativePath) { + this.logger.flush(resolvedObject.source.relativePath) } return impactedTargets; @@ -462,11 +466,11 @@ export class Targets { exports: [] }; - if (ileObject.extension === `binder`) { + if (ileObject.source && target.source.extension === `binder`) { const pathDetail = path.parse(localPath); if (this.suggestions.renames) { - this.logger.fileLog(ileObject.relativePath, { + this.logger.fileLog(ileObject.source.relativePath, { message: `Rename suggestion`, type: `rename`, change: { @@ -477,8 +481,8 @@ export class Targets { } }); } else { - this.logger.fileLog(ileObject.relativePath, { - message: `Extension is '${ileObject.extension}'. Consolidate by using 'bnd'?`, + this.logger.fileLog(ileObject.source.relativePath, { + message: `Extension is '${target.source.extension}'. Consolidate by using 'bnd'?`, type: `warning`, }); } @@ -501,7 +505,7 @@ export class Targets { if (symbolTokens.block && symbolTokens.block.length === 1 && symbolTokens.block[0].type === `word` && symbolTokens.block[0].value) { target.exports.push(trimQuotes(symbolTokens.block[0].value, `"`)); } else { - this.logger.fileLog(ileObject.relativePath, { + this.logger.fileLog(ileObject.source.relativePath, { message: `Invalid EXPORT found. Single quote string expected.`, type: `warning`, range: { @@ -533,7 +537,7 @@ export class Targets { deps: [] }; - infoOut(`${ileObject.systemName}.${ileObject.type}: ${ileObject.relativePath}`); + infoOut(`${ileObject.systemName}.${ileObject.type}: ${ileObject.source.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. @@ -560,14 +564,14 @@ export class Targets { alreadyFoundRefs.push(upperName); } else { - this.logger.fileLog(ileObject.relativePath, { + this.logger.fileLog(ileObject.source.relativePath, { message: `no object found for reference '${objectName}'`, type: `warning`, line: recordFormat.range.start }); } } else { - this.logger.fileLog(ileObject.relativePath, { + this.logger.fileLog(ileObject.source.relativePath, { message: `${currentKeyword} reference not included as possible reference to library found.`, type: `info`, line: recordFormat.range.start @@ -628,11 +632,11 @@ export class Targets { deps: [] }; - infoOut(`${ileObject.systemName}.${ileObject.type}: ${ileObject.relativePath}`); + infoOut(`${ileObject.systemName}.${ileObject.type}: ${ileObject.source.relativePath}`); - if (ileObject.extension?.toLowerCase() === `clp`) { + if (ileObject.source && ileObject.source.extension.toLowerCase() === `clp`) { if (this.suggestions.renames) { - this.logger.fileLog(ileObject.relativePath, { + this.logger.fileLog(ileObject.source.relativePath, { message: `Rename suggestion`, type: `rename`, change: { @@ -643,8 +647,8 @@ export class Targets { } }); } else { - this.logger.fileLog(ileObject.relativePath, { - message: `Extension is '${ileObject.extension}', but Source Orbit doesn't support CLP. Is it possible the extension should use '.pgm.clle'?`, + this.logger.fileLog(ileObject.source.relativePath, { + message: `Extension is '${ileObject.source.extension}', but Source Orbit doesn't support CLP. Is it possible the extension should use '.pgm.clle'?`, type: `warning`, }); } @@ -652,7 +656,7 @@ export class Targets { } else { if (ileObject.type === `MODULE`) { if (this.suggestions.renames) { - this.logger.fileLog(ileObject.relativePath, { + this.logger.fileLog(ileObject.source.relativePath, { message: `Rename suggestion`, type: `rename`, change: { @@ -663,7 +667,7 @@ export class Targets { } }); } else { - this.logger.fileLog(ileObject.relativePath, { + this.logger.fileLog(ileObject.source.relativePath, { message: `Type detected as ${ileObject.type} but Source Orbit doesn't support CL modules. Is it possible the extension should include '.pgm'?`, type: `warning`, }); @@ -678,7 +682,7 @@ export class Targets { const possibleObject = def.file; if (possibleObject) { if (possibleObject.library) { - this.logger.fileLog(ileObject.relativePath, { + this.logger.fileLog(ileObject.source.relativePath, { message: `Definition to ${possibleObject.library}/${possibleObject.name} ignored due to qualified path.`, range: { start: def.range.start, @@ -693,7 +697,7 @@ export class Targets { const resolvedPath = this.searchForObject({ systemName: possibleObject.name.toUpperCase(), type: `FILE` }); if (resolvedPath) target.deps.push(resolvedPath); else { - this.logger.fileLog(ileObject.relativePath, { + this.logger.fileLog(ileObject.source.relativePath, { message: `no object found for reference '${possibleObject.name}'`, range: { start: def.range.start, @@ -724,7 +728,7 @@ export class Targets { const resolvedPath = this.searchForObject({ systemName: name.toUpperCase(), type: `PGM` }); if (resolvedPath) target.deps.push(resolvedPath); else { - this.logger.fileLog(ileObject.relativePath, { + this.logger.fileLog(ileObject.source.relativePath, { message: `no object found for reference '${name}'`, range: { start: pgmParm.range.start, @@ -734,7 +738,7 @@ export class Targets { }); } } else { - this.logger.fileLog(ileObject.relativePath, { + this.logger.fileLog(ileObject.source.relativePath, { message: `PGM call not included as possible reference to library.`, range: { start: pgmParm.range.start, @@ -824,7 +828,7 @@ export class Targets { // if (currentTarget) { // info(`${currentTarget.name}.${currentTarget.type}`); - // info(`\tSource: ${currentTarget.relativePath}`); + // info(`\tSource: ${currentTarget.source.relativePath}`); // if (defs.length > 1) { // for (const def of defs.slice(1)) { @@ -854,8 +858,10 @@ export class Targets { longName: hasLongName, type: this.getObjectType(relativePath, mainDef.createType), text: options.text, - relativePath, - extension + source: { + relativePath, + extension + } } let suggestRename = false; @@ -875,7 +881,7 @@ export class Targets { // Let them know to use a system name in the create statement if one is not present if (ileObject.systemName.length > 10 && mainDef.object.system === undefined) { - this.logger.fileLog(ileObject.relativePath, { + this.logger.fileLog(ileObject.source.relativePath, { message: `${ileObject.systemName} (${ileObject.type}) name is longer than 10 characters. Consider using 'FOR SYSTEM NAME' in the CREATE statement.`, type: `warning`, range: { @@ -892,7 +898,7 @@ export class Targets { deps: [] }; - infoOut(`${newTarget.systemName}.${newTarget.type}: ${newTarget.relativePath}`); + infoOut(`${newTarget.systemName}.${newTarget.type}: ${newTarget.source.relativePath}`); // Now, let's go through all the other statements in this group (BEGIN/END) // and grab any references to other objects :eyes: @@ -912,7 +918,7 @@ export class Targets { const resolvedObject = this.searchForAnyObject({ name: simpleName, types: [`FILE`, `SRVPGM`, `PGM`] }); if (resolvedObject) newTarget.deps.push(resolvedObject); else if (!isSqlFunction(def.object.name)) { - this.logger.fileLog(newTarget.relativePath, { + this.logger.fileLog(newTarget.source.relativePath, { message: `No object found for reference '${def.object.name}'`, type: `warning`, range: { @@ -1013,7 +1019,7 @@ export class Targets { .map(ref => ref.name.toUpperCase()); } - infoOut(`${ileObject.systemName}.${ileObject.type}: ${ileObject.relativePath}`); + infoOut(`${ileObject.systemName}.${ileObject.type}: ${ileObject.source.relativePath}`); if (cache.includes && cache.includes.length > 0) { ileObject.headers = []; @@ -1059,7 +1065,7 @@ export class Targets { ileObject.headers.push(theIncludePath); if (this.suggestions.includes) { - this.logger.fileLog(ileObject.relativePath, { + this.logger.fileLog(ileObject.source.relativePath, { message: `Will update to use unix style path.`, type: `includeFix`, line: include.line, @@ -1068,7 +1074,7 @@ export class Targets { } }); } else { - this.logger.fileLog(ileObject.relativePath, { + this.logger.fileLog(ileObject.source.relativePath, { message: `Include at line ${include.line} found, to path '${theIncludePath}'`, type: `info`, line: include.line, @@ -1087,7 +1093,7 @@ export class Targets { const possibleName = pathDetail.name.toLowerCase().endsWith(`.pgm`) ? pathDetail.name.substring(0, pathDetail.name.length - 4) : pathDetail.name; if (this.suggestions.renames) { - this.logger.fileLog(ileObject.relativePath, { + this.logger.fileLog(ileObject.source.relativePath, { message: `Rename suggestion`, type: `rename`, change: { @@ -1098,7 +1104,7 @@ export class Targets { } }) } else { - this.logger.fileLog(ileObject.relativePath, { + this.logger.fileLog(ileObject.source.relativePath, { message: `type detected as ${ileObject.type} but NOMAIN keyword found.`, type: `warning`, }); @@ -1109,7 +1115,7 @@ export class Targets { // We need to do this for other language too down the line if (ileObject.type === `MODULE` && !cache.keyword[`NOMAIN`]) { if (this.suggestions.renames) { - this.logger.fileLog(ileObject.relativePath, { + this.logger.fileLog(ileObject.source.relativePath, { message: `Rename suggestion`, type: `rename`, change: { @@ -1120,7 +1126,7 @@ export class Targets { } }); } else { - this.logger.fileLog(ileObject.relativePath, { + this.logger.fileLog(ileObject.source.relativePath, { message: `type detected as ${ileObject.type} but NOMAIN keyword was not found. Is it possible the extension should include '.pgm'?`, type: `warning`, }); @@ -1128,7 +1134,7 @@ export class Targets { } if (cache.keyword[`BNDDIR`]) { - this.logger.fileLog(ileObject.relativePath, { + this.logger.fileLog(ileObject.source.relativePath, { message: `has the BNDDIR keyword. 'binders' property in iproj.json should be used instead.`, type: `info`, }); @@ -1166,7 +1172,7 @@ export class Targets { } else { - this.logger.fileLog(ileObject.relativePath, { + this.logger.fileLog(ileObject.source.relativePath, { message: `No object found for reference '${ref.lookup}'`, type: `warning`, line: ref.line @@ -1195,7 +1201,7 @@ export class Targets { const resolvedObject = this.searchForObject({ systemName: ref.lookup, type: `FILE` }); if (resolvedObject) target.deps.push(resolvedObject) else { - this.logger.fileLog(ileObject.relativePath, { + this.logger.fileLog(ileObject.source.relativePath, { message: `No object found for reference '${ref.lookup}'`, type: `warning`, line: ref.line @@ -1219,7 +1225,7 @@ export class Targets { if (extDescValue) { possibleName = trimQuotes(extDescValue); } else { - this.logger.fileLog(ileObject.relativePath, { + this.logger.fileLog(ileObject.source.relativePath, { message: `*EXTDESC is used for '${file.name}' but EXTDESC keyword not found`, type: `warning`, }); @@ -1240,7 +1246,7 @@ export class Targets { const resolvedObject = this.searchForObject({ systemName: ref.lookup, type: `FILE` }); if (resolvedObject) target.deps.push(resolvedObject) else { - this.logger.fileLog(ileObject.relativePath, { + this.logger.fileLog(ileObject.source.relativePath, { message: `No object found for reference '${ref.lookup}'`, type: `warning`, line: ref.line @@ -1261,7 +1267,7 @@ export class Targets { const resolvedObject = this.searchForObject({ systemName: ref.lookup, type: `FILE` }); if (resolvedObject) target.deps.push(resolvedObject) else if (!isSqlFunction(ref.lookup)) { - this.logger.fileLog(ileObject.relativePath, { + this.logger.fileLog(ileObject.source.relativePath, { message: `No object found for reference '${ref.lookup}'`, type: `warning`, line: ref.line @@ -1292,7 +1298,7 @@ export class Targets { const resolvedObject = this.searchForObject({ systemName: ref.lookup, type: `DTAARA` }); if (resolvedObject) target.deps.push(resolvedObject) else { - this.logger.fileLog(ileObject.relativePath, { + this.logger.fileLog(ileObject.source.relativePath, { message: `No object found for reference '${ref.lookup}'`, type: `warning`, line: ref.line @@ -1320,7 +1326,7 @@ export class Targets { const resolvedObject = this.searchForObject({ systemName: ref.lookup, type: `DTAARA` }); if (resolvedObject) target.deps.push(resolvedObject) else { - this.logger.fileLog(ileObject.relativePath, { + this.logger.fileLog(ileObject.source.relativePath, { message: `No object found for reference '${ref.lookup}'`, type: `warning`, line: ref.line @@ -1394,8 +1400,8 @@ export class Targets { // This service program target doesn't have any deps... so, it's not used? this.removeObject(target); - if (target.relativePath) { - this.logger.fileLog(target.relativePath, { + if (target.source.relativePath) { + this.logger.fileLog(target.source.relativePath, { message: `Removed as target because no modules were found with matching exports.`, type: `info` }); @@ -1483,7 +1489,7 @@ export class Targets { } else { this.removeObject(cmdObject); - this.logger.fileLog(cmdObject.relativePath, { + this.logger.fileLog(cmdObject.source.relativePath, { message: `Removed as target because no program was found with a matching name.`, type: `info` }); @@ -1492,12 +1498,14 @@ export class Targets { } private convertBoundProgramToMultiModuleProgram(currentTarget: ILEObjectTarget) { - const basePath = currentTarget.relativePath; + const basePath = currentTarget.source.relativePath; // First, let's change this current target to be solely a program // Change the extension so it's picked up correctly during the build process. - currentTarget.extension = `pgm`; - currentTarget.relativePath = undefined; + currentTarget.source = { + extension: `pgm`, + relativePath: undefined + } // Store a fake path for this program object this.storeResolved(path.join(this.cwd, `${currentTarget.systemName}.PGM`), currentTarget); @@ -1508,8 +1516,10 @@ export class Targets { imports: currentTarget.imports, exports: [], type: `MODULE`, - relativePath: basePath, - extension: path.extname(basePath).substring(1) + source: { + relativePath: basePath, + extension: path.extname(basePath).substring(1) + } }; // Replace the old resolved object with the module @@ -1583,8 +1593,8 @@ export class Targets { } return Object.values(this.resolvedObjects).filter(obj => - (obj.extension?.toUpperCase() === extension && (obj.type === `PGM`) === shouldBeProgram) || - (anyPrograms === true && obj.type === `PGM` && obj.extension.toUpperCase() === extension) + (obj.source?.extension.toUpperCase() === extension && (obj.type === `PGM`) === shouldBeProgram) || + (anyPrograms === true && obj.type === `PGM` && obj.source?.extension.toUpperCase() === extension) ); } diff --git a/cli/test/autofix.test.ts b/cli/test/autofix.test.ts index 06e2c8e..1fc62af 100644 --- a/cli/test/autofix.test.ts +++ b/cli/test/autofix.test.ts @@ -146,7 +146,7 @@ test(`Auto rename RPGLE program and include and fix-include infos`, async () => // Small test to check we can resolve the files const ddsTarget = targets.getTarget({systemName: `MSTDSP`, type: `FILE`}); expect(ddsTarget).toBeDefined(); - expect(ddsTarget?.relativePath).toBe(path.join(`qddssrc`, `mstdsp.dspf`)); + expect(ddsTarget?.source?.relativePath).toBe(path.join(`qddssrc`, `mstdsp.dspf`)); expect(ddsTarget.deps.length).toBe(3); }); diff --git a/cli/test/ddsDeps.test.ts b/cli/test/ddsDeps.test.ts index 44013c0..0674be7 100644 --- a/cli/test/ddsDeps.test.ts +++ b/cli/test/ddsDeps.test.ts @@ -44,7 +44,7 @@ describe(`dds_refs tests`, () => { expect(deps.length).toBe(1); expect(deps[0].systemName).toBe(`PROVIDER`); - const logs = targets.logger.getLogsFor(pro250d.relativePath); + const logs = targets.logger.getLogsFor(pro250d.source?.relativePath); expect(logs.length).toBe(1); expect(logs[0]).toMatchObject({ message: `no object found for reference 'COUNTRY'`, @@ -60,7 +60,7 @@ describe(`dds_refs tests`, () => { const deps = provider.deps; expect(deps.length).toBe(0); - const logs = targets.logger.getLogsFor(provider.relativePath); + const logs = targets.logger.getLogsFor(provider.source.relativePath); expect(logs.length).toBe(1); expect(logs[0]).toMatchObject({ message: `no object found for reference 'SAMREF'`, @@ -76,7 +76,7 @@ describe(`dds_refs tests`, () => { const deps = providerLf.deps; expect(deps.length).toBe(1); - const logs = targets.logger.getLogsFor(providerLf.relativePath); + const logs = targets.logger.getLogsFor(providerLf.source?.relativePath); expect(logs).toBeUndefined(); }); diff --git a/cli/test/ddsDepsWithRefFile.test.ts b/cli/test/ddsDepsWithRefFile.test.ts index 4157a38..6506d28 100644 --- a/cli/test/ddsDepsWithRefFile.test.ts +++ b/cli/test/ddsDepsWithRefFile.test.ts @@ -52,7 +52,7 @@ describe(`dds_refs tests with reference file`, () => { expect(deps.length).toBe(1); expect(deps[0].systemName).toBe(`PROVIDER`); - const logs = targets.logger.getLogsFor(pro250d.relativePath); + const logs = targets.logger.getLogsFor(pro250d.source?.relativePath); expect(logs.length).toBe(1); expect(logs[0]).toMatchObject({ message: `no object found for reference 'COUNTRY'`, @@ -68,7 +68,7 @@ describe(`dds_refs tests with reference file`, () => { const deps = provider.deps; expect(deps.length).toBe(1); - const logs = targets.logger.getLogsFor(provider.relativePath); + const logs = targets.logger.getLogsFor(provider.source?.relativePath); expect(logs).toBeUndefined(); }); @@ -79,7 +79,7 @@ describe(`dds_refs tests with reference file`, () => { const deps = providerLf.deps; expect(deps.length).toBe(1); - const logs = targets.logger.getLogsFor(providerLf.relativePath); + const logs = targets.logger.getLogsFor(providerLf.source?.relativePath); expect(logs).toBeUndefined(); }); diff --git a/cli/test/fixtures/targets.ts b/cli/test/fixtures/targets.ts index b81fb06..215d826 100644 --- a/cli/test/fixtures/targets.ts +++ b/cli/test/fixtures/targets.ts @@ -13,61 +13,61 @@ export async function baseTargets(withDeps = false) { const programACommand = await targets.resolvePathToObject(path.join(cwd, `qcmdsrc`, `programA.cmd`)); expect(programACommand.systemName).toBe(`PROGRAMA`); expect(programACommand.type).toBe(`CMD`); - expect(programACommand.extension).toBe(`cmd`); - expect(programACommand.relativePath).toBe(path.join(`qcmdsrc`, `programA.cmd`)); + expect(programACommand.source.extension).toBe(`cmd`); + expect(programACommand.source.relativePath).toBe(path.join(`qcmdsrc`, `programA.cmd`)); // Command object that goes unused. const unusedCmd = await targets.resolvePathToObject(path.join(cwd, `qcmdsrc`, `unused.cmd`)); expect(unusedCmd.systemName).toBe(`UNUSED`); expect(unusedCmd.type).toBe(`CMD`); - expect(unusedCmd.extension).toBe(`cmd`); - expect(unusedCmd.relativePath).toBe(path.join(`qcmdsrc`, `unused.cmd`)); + expect(unusedCmd.source.extension).toBe(`cmd`); + expect(unusedCmd.source.relativePath).toBe(path.join(`qcmdsrc`, `unused.cmd`)); // Program object const programA = await targets.resolvePathToObject(path.join(cwd, `qrpglesrc`, `programA.pgm.rpgle`)); expect(programA.systemName).toBe(`PROGRAMA`); expect(programA.type).toBe(`PGM`); - expect(programA.extension).toBe(`rpgle`); - expect(programA.relativePath).toBe(path.join(`qrpglesrc`, `programA.pgm.rpgle`)); + expect(programA.source.extension).toBe(`rpgle`); + expect(programA.source.relativePath).toBe(path.join(`qrpglesrc`, `programA.pgm.rpgle`)); // Program object, imports TOLOWER const programB = await targets.resolvePathToObject(path.join(cwd, `qrpglesrc`, `programB.pgm.sqlrpgle`)); expect(programB.systemName).toBe(`PROGRAMB`); expect(programB.type).toBe(`PGM`); - expect(programB.extension).toBe(`sqlrpgle`); - expect(programB.relativePath).toBe(path.join(`qrpglesrc`, `programB.pgm.sqlrpgle`)); + expect(programB.source.extension).toBe(`sqlrpgle`); + expect(programB.source.relativePath).toBe(path.join(`qrpglesrc`, `programB.pgm.sqlrpgle`)); programB.imports = [`TOLOWER`]; // Program object, imports TOLOWER const programC = await targets.resolvePathToObject(path.join(cwd, `qrpglesrc`, `programC.pgm.sqlrpgle`)); expect(programC.systemName).toBe(`PROGRAMC`); expect(programC.type).toBe(`PGM`); - expect(programC.extension).toBe(`sqlrpgle`); - expect(programC.relativePath).toBe(path.join(`qrpglesrc`, `programC.pgm.sqlrpgle`)); + expect(programC.source.extension).toBe(`sqlrpgle`); + expect(programC.source.relativePath).toBe(path.join(`qrpglesrc`, `programC.pgm.sqlrpgle`)); programC.imports = [`TOUPPER`]; // Module MODULEA.MODULE, which is not used at all. const moduleA = await targets.resolvePathToObject(path.join(cwd, `qrpglesrc`, `moduleA.rpgle`)); expect(moduleA.systemName).toBe(`MODULEA`); expect(moduleA.type).toBe(`MODULE`); - expect(moduleA.extension).toBe(`rpgle`); - expect(moduleA.relativePath).toBe(path.join(`qrpglesrc`, `moduleA.rpgle`)); + expect(moduleA.source.extension).toBe(`rpgle`); + expect(moduleA.source.relativePath).toBe(path.join(`qrpglesrc`, `moduleA.rpgle`)); moduleA.exports = [`SUMNUMS`]; // 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`)); + expect(moduleB.source.extension).toBe(`sqlrpgle`); + expect(moduleB.source.relativePath).toBe(path.join(`qrpglesrc`, `moduleB.sqlrpgle`)); moduleB.exports = [`TOLOWER`]; // SRVPGMA.SRVPGM, which imports TOLOWER from MODULEB.MODULE and therefore exports TOLOWER const srvpgmAModule = await targets.resolvePathToObject(path.join(cwd, `qsrvsrc`, `srvpgmA.bnd`)); expect(srvpgmAModule.systemName).toBe(`SRVPGMA`); expect(srvpgmAModule.type).toBe(`SRVPGM`); - expect(srvpgmAModule.extension).toBe(`bnd`); - expect(srvpgmAModule.relativePath).toBe(path.join(`qsrvsrc`, `srvpgmA.bnd`)); + expect(srvpgmAModule.source.extension).toBe(`bnd`); + expect(srvpgmAModule.source.relativePath).toBe(path.join(`qsrvsrc`, `srvpgmA.bnd`)); srvpgmAModule.imports = [`TOLOWER`]; srvpgmAModule.exports = [`TOLOWER`]; @@ -75,15 +75,15 @@ export async function baseTargets(withDeps = false) { const fileA = await targets.resolvePathToObject(path.join(cwd, `qddssrc`, `fileA.sql`)); expect(fileA.systemName).toBe(`FILEA`); expect(fileA.type).toBe(`FILE`); - expect(fileA.extension).toBe(`sql`); - expect(fileA.relativePath).toBe(path.join(`qddssrc`, `fileA.sql`)); + expect(fileA.source.extension).toBe(`sql`); + expect(fileA.source.relativePath).toBe(path.join(`qddssrc`, `fileA.sql`)); // FILEB.FILE const fileB = await targets.resolvePathToObject(path.join(cwd, `qddssrc`, `fileB.pf`)); expect(fileB.systemName).toBe(`FILEB`); expect(fileB.type).toBe(`FILE`); - expect(fileB.extension).toBe(`pf`); - expect(fileB.relativePath).toBe(path.join(`qddssrc`, `fileB.pf`)); + expect(fileB.source.extension).toBe(`pf`); + expect(fileB.source.relativePath).toBe(path.join(`qddssrc`, `fileB.pf`)); // ORDENTSRV.SRVPGM, which exports/imports FIXTOTALS const ORDENTSRV = await targets.resolvePathToObject(path.join(cwd, `qbndsrc`, `ordentsrv.binder`)); @@ -91,24 +91,24 @@ export async function baseTargets(withDeps = false) { ORDENTSRV.imports = [`FIXTOTALS`]; expect(ORDENTSRV.systemName).toBe(`ORDENTSRV`); expect(ORDENTSRV.type).toBe(`SRVPGM`); - expect(ORDENTSRV.extension).toBe(`binder`); - expect(ORDENTSRV.relativePath).toBe(path.join(`qbndsrc`, `ordentsrv.binder`)); + expect(ORDENTSRV.source.extension).toBe(`binder`); + expect(ORDENTSRV.source.relativePath).toBe(path.join(`qbndsrc`, `ordentsrv.binder`)); // ORDENTMOD.MODULE which exports FIXTOTALS const ORDENTMOD = await targets.resolvePathToObject(path.join(cwd, `qrpglesrc`, `ordentmod.rpgle`)); ORDENTMOD.exports = [`FIXTOTALS`]; expect(ORDENTMOD.systemName).toBe(`ORDENTMOD`); expect(ORDENTMOD.type).toBe(`MODULE`); - expect(ORDENTMOD.extension).toBe(`rpgle`); - expect(ORDENTMOD.relativePath).toBe(path.join(`qrpglesrc`, `ordentmod.rpgle`)); + expect(ORDENTMOD.source.extension).toBe(`rpgle`); + expect(ORDENTMOD.source.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`]; expect(UNUSEDSRV.systemName).toBe(`UNUSEDSRV`); expect(UNUSEDSRV.type).toBe(`SRVPGM`); - expect(UNUSEDSRV.extension).toBe(`binder`); - expect(UNUSEDSRV.relativePath).toBe(path.join(`qbndsrc`, `unusedsrv.binder`)); + expect(UNUSEDSRV.source.extension).toBe(`binder`); + expect(UNUSEDSRV.source.relativePath).toBe(path.join(`qbndsrc`, `unusedsrv.binder`)); if (withDeps) { targets.createOrAppend(programA, fileA); diff --git a/cli/test/includeMismatchFix.test.ts b/cli/test/includeMismatchFix.test.ts index 2379d47..b77e781 100644 --- a/cli/test/includeMismatchFix.test.ts +++ b/cli/test/includeMismatchFix.test.ts @@ -26,7 +26,7 @@ describe(`include_mismatch_fix tests`, () => { const articlePf = targets.getTarget({systemName: `ARTICLE`, type: `FILE`}); expect(articlePf).toBeDefined(); - const articlePfLogs = targets.logger.getLogsFor(articlePf.relativePath); + const articlePfLogs = targets.logger.getLogsFor(articlePf.source?.relativePath); expect(articlePfLogs.length).toBe(1); expect(articlePfLogs[0].message).toBe(`no object found for reference 'SAMREF'`); expect(articlePfLogs[0].type).toBe(`warning`); diff --git a/cli/test/mixedCaseExport.test.ts b/cli/test/mixedCaseExport.test.ts index 15d0e19..a538cd7 100644 --- a/cli/test/mixedCaseExport.test.ts +++ b/cli/test/mixedCaseExport.test.ts @@ -38,6 +38,6 @@ describe(`pr with mixed case exports exports `, () => { expect(srvPgmTarget.exports.length).toBe(3); expect(srvPgmTarget.exports).toStrictEqual(srvPgmTarget.deps[0].exports); - expect(allLogs[srvPgmObj.relativePath].length).toBe(0); + expect(allLogs[srvPgmObj.source.relativePath].length).toBe(0); }); }); \ No newline at end of file diff --git a/cli/test/project.test.ts b/cli/test/project.test.ts index 88e3283..aede88e 100644 --- a/cli/test/project.test.ts +++ b/cli/test/project.test.ts @@ -36,30 +36,30 @@ describe(`company_system tests`, () => { test(`Check mypgm`, async () => { const myPgm = targets.getTarget({systemName: `MYPGM`, type: `PGM`}); - expect(myPgm.relativePath).toBe(path.join(`qrpglesrc`, `mypgm.pgm.rpgle`)); + expect(myPgm.source.relativePath).toBe(path.join(`qrpglesrc`, `mypgm.pgm.rpgle`)); expect(myPgm.deps.length).toBe(0); }); test(`Check employees`, async () => { const myPgm = targets.getTarget({systemName: `EMPLOYEES`, type: `PGM`}); - expect(myPgm.relativePath).toBe(path.join(`qrpglesrc`, `employees.pgm.sqlrpgle`)); + expect(myPgm.source.relativePath).toBe(path.join(`qrpglesrc`, `employees.pgm.sqlrpgle`)); expect(myPgm.deps.length).toBe(2); const empTable = myPgm.deps[0]; expect(empTable.systemName).toBe(`EMPLOYEE`); expect(empTable.type).toBe(`FILE`); - expect(empTable.relativePath).toBe(path.join(`qddssrc`, `employee.table`)); + expect(empTable.source.relativePath).toBe(path.join(`qddssrc`, `employee.table`)); const empDisplay = myPgm.deps[1]; expect(empDisplay.systemName).toBe(`EMPS`); expect(empDisplay.type).toBe(`FILE`); - expect(empDisplay.relativePath).toBe(path.join(`qddssrc`, `emps.dspf`)); + expect(empDisplay.source.relativePath).toBe(path.join(`qddssrc`, `emps.dspf`)); }); test(`Check depts`, async () => { const myPgm = targets.getTarget({systemName: `DEPTS`, type: `PGM`}); - expect(myPgm.relativePath).toBe(path.join(`qrpglesrc`, `depts.pgm.sqlrpgle`)); + expect(myPgm.source.relativePath).toBe(path.join(`qrpglesrc`, `depts.pgm.sqlrpgle`)); expect(myPgm.text).toBe(`This is the text for this program`); expect(myPgm.deps.length).toBe(4); @@ -67,22 +67,22 @@ describe(`company_system tests`, () => { const empPgm = myPgm.deps[0]; expect(empPgm.systemName).toBe(`EMPLOYEES`); expect(empPgm.type).toBe(`PGM`); - expect(empPgm.relativePath).toBe(path.join(`qrpglesrc`, `employees.pgm.sqlrpgle`)); + expect(empPgm.source.relativePath).toBe(path.join(`qrpglesrc`, `employees.pgm.sqlrpgle`)); const deptTable = myPgm.deps[1]; expect(deptTable.systemName).toBe(`DEPARTMENT`); expect(deptTable.type).toBe(`FILE`); - expect(deptTable.relativePath).toBe(path.join(`qddssrc`, `department.table`)); + expect(deptTable.source.relativePath).toBe(path.join(`qddssrc`, `department.table`)); const deptFile = myPgm.deps[2]; expect(deptFile.systemName).toBe(`DEPTS`); expect(deptFile.type).toBe(`FILE`); - expect(deptFile.relativePath).toBe(path.join(`qddssrc`, `depts.dspf`)); + expect(deptFile.source.relativePath).toBe(path.join(`qddssrc`, `depts.dspf`)); const utilsSrvPgm = myPgm.deps[3]; expect(utilsSrvPgm.systemName).toBe(`UTILS`); expect(utilsSrvPgm.type).toBe(`SRVPGM`); - expect(utilsSrvPgm.relativePath).toBe(path.join(`qsrvsrc`, `utils.bnd`)); + expect(utilsSrvPgm.source.relativePath).toBe(path.join(`qsrvsrc`, `utils.bnd`)); expect(myPgm.headers).toBeDefined(); expect(myPgm.headers.length).toBe(2); @@ -91,28 +91,28 @@ describe(`company_system tests`, () => { test(`Check utils`, async () => { const myPgm = targets.getTarget({systemName: `UTILS`, type: `SRVPGM`}); - expect(myPgm.relativePath).toBe(path.join(`qsrvsrc`, `utils.bnd`)); + expect(myPgm.source.relativePath).toBe(path.join(`qsrvsrc`, `utils.bnd`)); expect(myPgm.deps.length).toBe(1); const empPgm = myPgm.deps[0]; expect(empPgm.systemName).toBe(`UTILS`); expect(empPgm.type).toBe(`MODULE`); - expect(empPgm.relativePath).toBe(path.join(`qrpglesrc`, `utils.sqlrpgle`)); + expect(empPgm.source.relativePath).toBe(path.join(`qrpglesrc`, `utils.sqlrpgle`)); }); test(`Check getDouble`, async () => { const theObj = targets.getTarget({systemName: `GETDOUBLE`, type: `SRVPGM`}); - expect(theObj.relativePath).toBe(path.join(`qsqlsrc`, `getDouble.sql`)); + expect(theObj.source.relativePath).toBe(path.join(`qsqlsrc`, `getDouble.sql`)); expect(theObj.deps.length).toBe(1); const dep = theObj.deps[0]; expect(dep.systemName).toBe(`BANKING`); expect(dep.type).toBe(`SRVPGM`); - expect(dep.relativePath).toBe(path.join(`qsrvsrc`, `banking.bnd`)); + expect(dep.source.relativePath).toBe(path.join(`qsrvsrc`, `banking.bnd`)); - const logs = targets.logger.getLogsFor(theObj.relativePath); + const logs = targets.logger.getLogsFor(theObj.source.relativePath); expect(logs.length).toBe(1); expect(logs[0].message).toBe(`Extension should be based on type. Suggested name is 'getdouble.sqludf'`); expect(logs[0].type).toBe(`warning`); @@ -120,24 +120,24 @@ describe(`company_system tests`, () => { test(`Check binding directory`, async () => { const myBinder = targets.getTarget({systemName: `$(APP_BNDDIR)`, type: `BNDDIR`}); - expect(myBinder.relativePath).toBeUndefined(); + expect(myBinder.source).toBeUndefined(); expect(myBinder.deps.length).toBe(2); const bankingSrvpgm = myBinder.deps.find(d => d.systemName === `BANKING`); expect(bankingSrvpgm.systemName).toBe(`BANKING`); expect(bankingSrvpgm.type).toBe(`SRVPGM`); - expect(bankingSrvpgm.relativePath).toBe(path.join(`qsrvsrc`, `banking.bnd`)); + expect(bankingSrvpgm.source.relativePath).toBe(path.join(`qsrvsrc`, `banking.bnd`)); const utilsSrvpgm = myBinder.deps.find(d => d.systemName === `UTILS`); expect(utilsSrvpgm.systemName).toBe(`UTILS`); expect(utilsSrvpgm.type).toBe(`SRVPGM`); - expect(utilsSrvpgm.relativePath).toBe(path.join(`qsrvsrc`, `utils.bnd`)); + expect(utilsSrvpgm.source.relativePath).toBe(path.join(`qsrvsrc`, `utils.bnd`)); }); test(`Check employee table`, async () => { const empTable = targets.getTarget({systemName: `EMPLOYEE`, type: `FILE`}); - expect(empTable.relativePath).toBe(path.join(`qddssrc`, `employee.table`)); + expect(empTable.source.relativePath).toBe(path.join(`qddssrc`, `employee.table`)); expect(empTable.text).toBe(`Employee File`); }); @@ -147,18 +147,18 @@ describe(`company_system tests`, () => { let deptsPgm = targets.getTarget({systemName: `DEPTS`, type: `PGM`}); let deptsFile = targets.getTarget({systemName: `DEPTS`, type: `FILE`}); - const deptsFilePath = path.join(project.cwd, deptsFile.relativePath); - const deptsPgmPath = path.join(project.cwd, deptsPgm.relativePath); + const deptsFilePath = path.join(project.cwd, deptsFile.source.relativePath); + const deptsPgmPath = path.join(project.cwd, deptsPgm.source.relativePath); - targets.logger.flush(deptsFile.relativePath); + targets.logger.flush(deptsFile.source.relativePath); // We removed the DEPTS display file, used by DEPTS program - const impacted = targets.removeObjectByPath(path.join(project.cwd, deptsFile.relativePath)); + const impacted = targets.removeObjectByPath(path.join(project.cwd, deptsFile.source.relativePath)); expect(impacted.length).toBe(1); expect(impacted[0].systemName).toBe(`DEPTS`); expect(impacted[0].type).toBe(`PGM`); - const logs = targets.logger.getLogsFor(deptsPgm.relativePath); + const logs = targets.logger.getLogsFor(deptsPgm.source.relativePath); expect(logs.length).toBe(3); expect(logs[0].message).toBe(`Include at line 13 found, to path 'qrpgleref/constants.rpgleinc'`); expect(logs[1].message).toBe(`Include at line 14 found, to path 'qrpgleref/utils.rpgleinc'`); @@ -372,7 +372,7 @@ describe(`company_system tests`, () => { test(`Impact of EMPLOYEES`, () => { const empPgm = targets.getTarget({systemName: `EMPLOYEES`, type: `PGM`}); - expect(empPgm.relativePath).toBe(path.join(`qrpglesrc`, `employees.pgm.sqlrpgle`)); + expect(empPgm.source.relativePath).toBe(path.join(`qrpglesrc`, `employees.pgm.sqlrpgle`)); const impactTree = targets.getImpactFor(empPgm); expect(impactTree.ileObject.systemName).toBe(`EMPLOYEES`); @@ -387,7 +387,7 @@ describe(`company_system tests`, () => { test(`Impact of UTILS`, () => { const utilsModule = targets.getTarget({systemName: `UTILS`, type: `MODULE`}); - expect(utilsModule.relativePath).toBe(path.join(`qrpglesrc`, `utils.sqlrpgle`)); + expect(utilsModule.source.relativePath).toBe(path.join(`qrpglesrc`, `utils.sqlrpgle`)); const impactTree = targets.getImpactFor(utilsModule); expect(impactTree.ileObject.systemName).toBe(`UTILS`); @@ -424,7 +424,7 @@ describe(`company_system tests`, () => { // from regular objects. const resolvedObject = targets.getTarget({systemName: `GETTOTSAL`, type: `SRVPGM`}); - expect(resolvedObject.relativePath).toBe(path.join(`qsqlsrc`, `getTotalSalary.sqludf`)); + expect(resolvedObject.source.relativePath).toBe(path.join(`qsqlsrc`, `getTotalSalary.sqludf`)); expect(resolvedObject).toBeDefined(); expect(resolvedObject.systemName).toBe(`GETTOTSAL`); @@ -434,7 +434,7 @@ describe(`company_system tests`, () => { expect(resolvedObject.deps.length).toBe(1); expect(resolvedObject.deps[0].systemName).toBe(`EMPLOYEE`); - const logs = targets.logger.getLogsFor(resolvedObject.relativePath); + const logs = targets.logger.getLogsFor(resolvedObject.source.relativePath); console.log(logs); expect(logs.length).toBe(0); // expect(logs[0].message).toBe(`Extension should be based on type. Suggested name is 'getTotalSalary.sqludf'`); diff --git a/cli/test/project2.test.ts b/cli/test/project2.test.ts index 3dcc83a..28282be 100644 --- a/cli/test/project2.test.ts +++ b/cli/test/project2.test.ts @@ -35,30 +35,30 @@ describe(`company_system tests`, () => { test(`Check mypgm`, async () => { const myPgm = targets.getTarget({systemName: `MYPGM`, type: `PGM`}); - expect(myPgm.relativePath).toBe(path.join(`qrpglesrc`, `mypgm.rpgle`)); + expect(myPgm.source.relativePath).toBe(path.join(`qrpglesrc`, `mypgm.rpgle`)); expect(myPgm.deps.length).toBe(0); }); test(`Check employees`, async () => { const myPgm = targets.getTarget({systemName: `EMPLOYEES`, type: `PGM`}); - expect(myPgm.relativePath).toBe(path.join(`qrpglesrc`, `employees.sqlrpgle`)); + expect(myPgm.source.relativePath).toBe(path.join(`qrpglesrc`, `employees.sqlrpgle`)); expect(myPgm.deps.length).toBe(2); const empTable = myPgm.deps[0]; expect(empTable.systemName).toBe(`EMPLOYEE`); expect(empTable.type).toBe(`FILE`); - expect(empTable.relativePath).toBe(path.join(`qddssrc`, `employee.table`)); + expect(empTable.source.relativePath).toBe(path.join(`qddssrc`, `employee.table`)); const empDisplay = myPgm.deps[1]; expect(empDisplay.systemName).toBe(`EMPS`); expect(empDisplay.type).toBe(`FILE`); - expect(empDisplay.relativePath).toBe(path.join(`qddssrc`, `emps.dspf`)); + expect(empDisplay.source.relativePath).toBe(path.join(`qddssrc`, `emps.dspf`)); }); test(`Check depts`, async () => { const myPgm = targets.getTarget({systemName: `DEPTS`, type: `PGM`}); - expect(myPgm.relativePath).toBe(path.join(`qrpglesrc`, `depts.sqlrpgle`)); + expect(myPgm.source.relativePath).toBe(path.join(`qrpglesrc`, `depts.sqlrpgle`)); expect(myPgm.text).toBe(`This is the text for this program`); expect(myPgm.deps.length).toBe(4); @@ -66,48 +66,48 @@ describe(`company_system tests`, () => { const empPgm = myPgm.deps[0]; expect(empPgm.systemName).toBe(`EMPLOYEES`); expect(empPgm.type).toBe(`PGM`); - expect(empPgm.relativePath).toBe(path.join(`qrpglesrc`, `employees.sqlrpgle`)); + expect(empPgm.source.relativePath).toBe(path.join(`qrpglesrc`, `employees.sqlrpgle`)); const deptTable = myPgm.deps[1]; expect(deptTable.systemName).toBe(`DEPARTMENT`); expect(deptTable.type).toBe(`FILE`); - expect(deptTable.relativePath).toBe(path.join(`qddssrc`, `department.table`)); + expect(deptTable.source.relativePath).toBe(path.join(`qddssrc`, `department.table`)); const deptFile = myPgm.deps[2]; expect(deptFile.systemName).toBe(`DEPTS`); expect(deptFile.type).toBe(`FILE`); - expect(deptFile.relativePath).toBe(path.join(`qddssrc`, `depts.dspf`)); + expect(deptFile.source.relativePath).toBe(path.join(`qddssrc`, `depts.dspf`)); const utilsSrvPgm = myPgm.deps[3]; expect(utilsSrvPgm.systemName).toBe(`UTILS`); expect(utilsSrvPgm.type).toBe(`SRVPGM`); - expect(utilsSrvPgm.relativePath).toBe(path.join(`qsrvsrc`, `utils.bnd`)); + expect(utilsSrvPgm.source.relativePath).toBe(path.join(`qsrvsrc`, `utils.bnd`)); }); test(`Check utils`, async () => { const myPgm = targets.getTarget({systemName: `UTILS`, type: `SRVPGM`}); - expect(myPgm.relativePath).toBe(path.join(`qsrvsrc`, `utils.bnd`)); + expect(myPgm.source.relativePath).toBe(path.join(`qsrvsrc`, `utils.bnd`)); expect(myPgm.deps.length).toBe(1); const empPgm = myPgm.deps[0]; expect(empPgm.systemName).toBe(`UTILS`); expect(empPgm.type).toBe(`MODULE`); - expect(empPgm.relativePath).toBe(path.join(`qrpglesrc`, `utils.sqlrpgle`)); + expect(empPgm.source.relativePath).toBe(path.join(`qrpglesrc`, `utils.sqlrpgle`)); }); test(`Check getDouble`, async () => { const theObj = targets.getTarget({systemName: `GETDOUBLE`, type: `SRVPGM`}); - expect(theObj.relativePath).toBe(path.join(`qsqlsrc`, `getDouble.sql`)); + expect(theObj.source.relativePath).toBe(path.join(`qsqlsrc`, `getDouble.sql`)); expect(theObj.deps.length).toBe(1); const dep = theObj.deps[0]; expect(dep.systemName).toBe(`BANKING`); expect(dep.type).toBe(`SRVPGM`); - expect(dep.relativePath).toBe(path.join(`qsrvsrc`, `banking.bnd`)); + expect(dep.source.relativePath).toBe(path.join(`qsrvsrc`, `banking.bnd`)); - const logs = targets.logger.getLogsFor(theObj.relativePath); + const logs = targets.logger.getLogsFor(theObj.source.relativePath); expect(logs.length).toBe(1); expect(logs[0].message).toBe(`Extension should be based on type. Suggested name is 'getdouble.sqludf'`); expect(logs[0].type).toBe(`warning`); @@ -115,24 +115,24 @@ describe(`company_system tests`, () => { test(`Check binding directory`, async () => { const myBinder = targets.getTarget({systemName: `$(APP_BNDDIR)`, type: `BNDDIR`}); - expect(myBinder.relativePath).toBeUndefined(); + expect(myBinder.source).toBeUndefined(); expect(myBinder.deps.length).toBe(2); const bankingSrvpgm = myBinder.deps.find(d => d.systemName === `BANKING`); expect(bankingSrvpgm.systemName).toBe(`BANKING`); expect(bankingSrvpgm.type).toBe(`SRVPGM`); - expect(bankingSrvpgm.relativePath).toBe(path.join(`qsrvsrc`, `banking.bnd`)); + expect(bankingSrvpgm.source.relativePath).toBe(path.join(`qsrvsrc`, `banking.bnd`)); const utilsSrvpgm = myBinder.deps.find(d => d.systemName === `UTILS`); expect(utilsSrvpgm.systemName).toBe(`UTILS`); expect(utilsSrvpgm.type).toBe(`SRVPGM`); - expect(utilsSrvpgm.relativePath).toBe(path.join(`qsrvsrc`, `utils.bnd`)); + expect(utilsSrvpgm.source.relativePath).toBe(path.join(`qsrvsrc`, `utils.bnd`)); }); test(`Check employee table`, async () => { const empTable = targets.getTarget({systemName: `EMPLOYEE`, type: `FILE`}); - expect(empTable.relativePath).toBe(path.join(`qddssrc`, `employee.table`)); + expect(empTable.source.relativePath).toBe(path.join(`qddssrc`, `employee.table`)); expect(empTable.text).toBe(`Employee File`); }); @@ -142,18 +142,18 @@ describe(`company_system tests`, () => { let deptsPgm = targets.getTarget({systemName: `DEPTS`, type: `PGM`}); let deptsFile = targets.getTarget({systemName: `DEPTS`, type: `FILE`}); - const deptsFilePath = path.join(project.cwd, deptsFile.relativePath); - const deptsPgmPath = path.join(project.cwd, deptsPgm.relativePath); + const deptsFilePath = path.join(project.cwd, deptsFile.source.relativePath); + const deptsPgmPath = path.join(project.cwd, deptsPgm.source.relativePath); - targets.logger.flush(deptsFile.relativePath); + targets.logger.flush(deptsFile.source.relativePath); // We removed the DEPTS display file, used by DEPTS program - const impacted = targets.removeObjectByPath(path.join(project.cwd, deptsFile.relativePath)); + const impacted = targets.removeObjectByPath(path.join(project.cwd, deptsFile.source.relativePath)); expect(impacted.length).toBe(1); expect(impacted[0].systemName).toBe(`DEPTS`); expect(impacted[0].type).toBe(`PGM`); - const logs = targets.logger.getLogsFor(deptsPgm.relativePath); + const logs = targets.logger.getLogsFor(deptsPgm.source.relativePath); expect(logs.length).toBe(3); expect(logs[0].message).toBe(`Include at line 13 found, to path 'qrpgleref/constants.rpgleinc'`); expect(logs[1].message).toBe(`Include at line 14 found, to path 'qrpgleref/utils.rpgleinc'`); @@ -367,7 +367,7 @@ describe(`company_system tests`, () => { test(`Impact of EMPLOYEES`, () => { const empPgm = targets.getTarget({systemName: `EMPLOYEES`, type: `PGM`}); - expect(empPgm.relativePath).toBe(path.join(`qrpglesrc`, `employees.sqlrpgle`)); + expect(empPgm.source.relativePath).toBe(path.join(`qrpglesrc`, `employees.sqlrpgle`)); const impactTree = targets.getImpactFor(empPgm); expect(impactTree.ileObject.systemName).toBe(`EMPLOYEES`); @@ -382,7 +382,7 @@ describe(`company_system tests`, () => { test(`Impact of UTILS`, () => { const utilsModule = targets.getTarget({systemName: `UTILS`, type: `MODULE`}); - expect(utilsModule.relativePath).toBe(path.join(`qrpglesrc`, `utils.sqlrpgle`)); + expect(utilsModule.source.relativePath).toBe(path.join(`qrpglesrc`, `utils.sqlrpgle`)); const impactTree = targets.getImpactFor(utilsModule); expect(impactTree.ileObject.systemName).toBe(`UTILS`); @@ -419,7 +419,7 @@ describe(`company_system tests`, () => { // from regular objects. const resolvedObject = targets.getTarget({systemName: `GETTOTSAL`, type: `SRVPGM`}); - expect(resolvedObject.relativePath).toBe(path.join(`qsqlsrc`, `getTotalSalary.sqludf`)); + expect(resolvedObject.source.relativePath).toBe(path.join(`qsqlsrc`, `getTotalSalary.sqludf`)); expect(resolvedObject).toBeDefined(); expect(resolvedObject.systemName).toBe(`GETTOTSAL`); @@ -429,7 +429,7 @@ describe(`company_system tests`, () => { expect(resolvedObject.deps.length).toBe(1); expect(resolvedObject.deps[0].systemName).toBe(`EMPLOYEE`); - const logs = targets.logger.getLogsFor(resolvedObject.relativePath); + const logs = targets.logger.getLogsFor(resolvedObject.source.relativePath); console.log(logs); expect(logs.length).toBe(0); // expect(logs[0].message).toBe(`Extension should be based on type. Suggested name is 'getTotalSalary.sqludf'`); diff --git a/cli/test/sqlLongNames.test.ts b/cli/test/sqlLongNames.test.ts index 8bdaff6..cb4bda5 100644 --- a/cli/test/sqlLongNames.test.ts +++ b/cli/test/sqlLongNames.test.ts @@ -35,7 +35,7 @@ describe(`sql long name lookup`, () => { expect(trans).toMatchObject(transaction); - const moduleLogs = targets.logger.getLogsFor(trans.relativePath); + const moduleLogs = targets.logger.getLogsFor(trans.source.relativePath); expect(moduleLogs).toBeUndefined(); }); diff --git a/cli/test/sqlReference.test.ts b/cli/test/sqlReference.test.ts index 21a2d6b..76104f1 100644 --- a/cli/test/sqlReference.test.ts +++ b/cli/test/sqlReference.test.ts @@ -26,12 +26,12 @@ describe(`sql_references tests (internal scope analysis)`, () => { test(`Check stock (with internal scope analysis)`, async () => { const myPgm = targets.getTarget({ systemName: `SQLREFPGM`, type: `PGM` }); - expect(myPgm.relativePath).toBe(path.join(`qrpglesrc`, `sqlrefpgm.pgm.sqlrpgle`)); + expect(myPgm.source.relativePath).toBe(path.join(`qrpglesrc`, `sqlrefpgm.pgm.sqlrpgle`)); expect(myPgm.deps.length).toBe(1); const empTable = myPgm.deps[0]; expect(empTable.systemName).toBe(`STOCK`); expect(empTable.type).toBe(`FILE`); - expect(empTable.relativePath).toBe(path.join(`qddssrc`, `stock.table`)); + expect(empTable.source.relativePath).toBe(path.join(`qddssrc`, `stock.table`)); }); }); \ No newline at end of file diff --git a/cli/test/sqlReferenceWith.test.ts b/cli/test/sqlReferenceWith.test.ts index 272753a..d3aa0ac 100644 --- a/cli/test/sqlReferenceWith.test.ts +++ b/cli/test/sqlReferenceWith.test.ts @@ -25,9 +25,9 @@ describe(`sql_references_with tests`, () => { test(`SQL with clause`, async () => { const myPgm = targets.getTarget({ systemName: `SQLWITHPGM`, type: `PGM` }); - expect(myPgm.relativePath).toBe(path.join(`sqlwithpgm.pgm.sqlrpgle`)); + expect(myPgm.source.relativePath).toBe(path.join(`sqlwithpgm.pgm.sqlrpgle`)); - const moduleLogs = targets.logger.getLogsFor(myPgm.relativePath); + const moduleLogs = targets.logger.getLogsFor(myPgm.source.relativePath); expect(moduleLogs.length).toBe(1); expect(moduleLogs[0].message).toBe(`No object found for reference 'TABLE1'`); }); diff --git a/cli/test/targets.test.ts b/cli/test/targets.test.ts index bcb8af0..804f2bb 100644 --- a/cli/test/targets.test.ts +++ b/cli/test/targets.test.ts @@ -51,12 +51,12 @@ test('resolveBinder', async () => { expect(targets.getTarget({systemName: `UNUSEDSRV`, type: `SRVPGM`})).toBeUndefined(); expect(targets.getTarget({systemName: `UNUSED`, type: `CMD`})).toBeUndefined(); - const unusedSrvLogs = targets.logger.getLogsFor(unusedSrvPgm.relativePath); + const unusedSrvLogs = targets.logger.getLogsFor(unusedSrvPgm.source.relativePath); expect(unusedSrvLogs.length).toBe(1); expect(unusedSrvLogs[0].message).toBe(`Removed as target because no modules were found with matching exports.`); expect(unusedSrvLogs[0].type).toBe(`info`); - const unusedCmdLogs = targets.logger.getLogsFor(unusedCmd.relativePath); + const unusedCmdLogs = targets.logger.getLogsFor(unusedCmd.source.relativePath); expect(unusedCmdLogs.length).toBe(1); expect(unusedCmdLogs[0].message).toBe(`Removed as target because no program was found with a matching name.`); expect(unusedCmdLogs[0].type).toBe(`info`); @@ -68,8 +68,7 @@ test('resolveBinder', async () => { const bnddir = deps.find(d => d.systemName === `$(APP_BNDDIR)` && d.type === `BNDDIR`); expect(bnddir).toBeDefined(); - expect(bnddir.extension).toBeUndefined(); - expect(bnddir.relativePath).toBeUndefined(); + expect(bnddir.source).toBeUndefined(); expect(bnddir.deps.length).toBe(2); for (const srvPgmDep of bnddir.deps) { @@ -79,8 +78,8 @@ test('resolveBinder', async () => { expect(srvPgm).toBeDefined(); expect(srvPgm.deps.length).toBe(1); - expect(srvPgm.relativePath).toBeDefined(); - expect(srvPgm.extension).toBeDefined(); + expect(srvPgm.source.relativePath).toBeDefined(); + expect(srvPgm.source.extension).toBeDefined(); } const programACmd = deps.find(d => d.systemName === `PROGRAMA` && d.type === `CMD`); @@ -89,17 +88,17 @@ test('resolveBinder', async () => { expect(programACmd.deps[0].type).toBe(`PGM`); }); -test('getObjectsByExtension', async () => { +test('getObjectsBysource.Extension', async () => { const targets = await baseTargets(true); const rpglePrograms = targets.getResolvedObjectsByFileExtension(`pgm.rpgle`); expect(rpglePrograms.length).toBe(1); - expect(rpglePrograms[0].relativePath).toBe(path.join(`qrpglesrc`, `programA.pgm.rpgle`)); + expect(rpglePrograms[0].source.relativePath).toBe(path.join(`qrpglesrc`, `programA.pgm.rpgle`)); const rpgleModules = targets.getResolvedObjectsByFileExtension(`rpgle`); expect(rpgleModules.length).toBe(2); - expect(rpgleModules[0].relativePath).toBe(path.join(`qrpglesrc`, `moduleA.rpgle`)); - expect(rpgleModules[1].relativePath).toBe(path.join(`qrpglesrc`, `ordentmod.rpgle`)); + expect(rpgleModules[0].source.relativePath).toBe(path.join(`qrpglesrc`, `moduleA.rpgle`)); + expect(rpgleModules[1].source.relativePath).toBe(path.join(`qrpglesrc`, `ordentmod.rpgle`)); }) test(`Multi-module program and service programs`, async () => { @@ -116,10 +115,10 @@ test(`Multi-module program and service programs`, async () => { expect(modules.length).toBe(5); const webappPgm = programs[0]; - expect(webappPgm.extension).toBe(`pgm`); + expect(webappPgm.source.extension).toBe(`pgm`); const webappDef = deps.find(d => d.systemName === webappPgm.systemName && d.type === webappPgm.type); expect(webappDef).toBeDefined(); - expect(webappDef.extension).toBe(`pgm`); + expect(webappDef.source.extension).toBe(`pgm`); expect(webappDef.deps.length).toBe(4); expect(webappDef.deps.filter(d => d.type === `MODULE`).length).toBe(3); From b1b513bf64256c1368817dea43926a8a2ed3ef95 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Tue, 1 Apr 2025 14:24:08 -0400 Subject: [PATCH 02/18] Collect RPGLE references Signed-off-by: worksofliam --- cli/src/languages/rpgle.ts | 70 +++++++++++++++++++++++++++++++++++++- cli/src/targets.ts | 37 ++++++++++++++++---- 2 files changed, 99 insertions(+), 8 deletions(-) diff --git a/cli/src/languages/rpgle.ts b/cli/src/languages/rpgle.ts index c2a3b62..9b754fd 100644 --- a/cli/src/languages/rpgle.ts +++ b/cli/src/languages/rpgle.ts @@ -3,7 +3,9 @@ import { readFileSync } from 'fs'; import * as path from 'path'; import Parser from "vscode-rpgle/language/parser"; -import { Targets } from '../targets'; +import { SourceSymbol, SymbolReferences, Targets } from '../targets'; +import Cache from "vscode-rpgle/language/models/cache"; +import Declaration from 'vscode-rpgle/language/models/declaration'; let includeFileCache: { [path: string]: string } = {}; @@ -75,4 +77,70 @@ export function setupParser(targets: Targets): Parser { }); return parser; +} + +export function toSymbolRefs(def: Declaration): SymbolReferences { + if (def.references.length > 0) { + let refs: SymbolReferences = {}; + for (const ref of def.references) { + if (!refs[ref.uri]) { + refs[ref.uri] = []; + } + refs[ref.uri].push(ref.offset); + } + + return refs; + } + + return {}; +} + +export function rpgleDocToSymbolList(doc: Cache): SourceSymbol[] { + let symbols: SourceSymbol[] = [] + + const allDefs = [ + ...doc.constants, + ...doc.files, + ...doc.sqlReferences, + ...doc.structs, + ...doc.subroutines, + ...doc.variables + ]; + + for (const def of allDefs) { + let newSymbol: SourceSymbol = { + name: def.name, + type: def.type, + relativePath: def.position.path, // TODO: make sure this is relative!! + references: toSymbolRefs(def) + } + + if (def.subItems.length > 0) { + newSymbol.children = def.subItems.map(sub => { + return { + name: sub.name, + type: sub.type, + relativePath: def.position.path, // subitems are in the same file + references: toSymbolRefs(def) + }; + }); + } + + symbols.push(newSymbol); + } + + for (const proc of doc.procedures) { + let newSymbol: SourceSymbol = { + name: proc.name, + type: `procedure`, + relativePath: proc.position.path, // TODO: make sure this is relative!! + references: toSymbolRefs(proc) + }; + + newSymbol.children = rpgleDocToSymbolList(proc.scope); + + symbols.push(newSymbol); + } + + return symbols; } \ No newline at end of file diff --git a/cli/src/targets.ts b/cli/src/targets.ts index 6d255e3..3060b3f 100644 --- a/cli/src/targets.ts +++ b/cli/src/targets.ts @@ -9,7 +9,7 @@ import Document from "vscode-db2i/src/language/sql/document"; import { ObjectRef, StatementType } from 'vscode-db2i/src/language/sql/types'; import { rpgExtensions, clExtensions, ddsExtension, sqlExtensions, srvPgmExtensions, cmdExtensions } from './extensions'; import Parser from "vscode-rpgle/language/parser"; -import { setupParser } from './languages/rpgle'; +import { rpgleDocToSymbolList, setupParser } from './languages/rpgle'; import { Logger } from './logger'; import { asPosix, getReferenceObjectsFrom, getSystemNameFromPath, toLocalPath } from './utils'; import { extCanBeProgram, getObjectType } from './builders/environment'; @@ -32,7 +32,19 @@ const sqlTypeExtension = { const bindingDirectoryTarget: ILEObject = { systemName: `$(APP_BNDDIR)`, type: `BNDDIR` }; -const TextRegex = /\%TEXT.*(?=\n|\*)/gm +const TextRegex = /\%TEXT.*(?=\n|\*)/gm; + +export type SymbolReferences = { + [relativePath: string]: {start: number, end: number}[] +}; + +export interface SourceSymbol { + name: string; + type: string; + relativePath: string; + children?: SourceSymbol[], + references: SymbolReferences; +} export interface ILEObject { systemName: string; @@ -42,6 +54,7 @@ export interface ILEObject { source?: { relativePath: string; extension: string; + symbols: SourceSymbol[]; } reference?: boolean; @@ -105,6 +118,7 @@ export class Targets { private resolvedExports: { [name: string]: ILEObject } = {}; private targets: { [name: string]: ILEObjectTarget } = {}; private needsBinder = false; + private withReferences = true; private suggestions: TargetSuggestions = {}; @@ -170,7 +184,8 @@ export class Targets { text: newText, source: { relativePath, - extension + extension, + symbols: [], } }; @@ -388,12 +403,17 @@ export class Targets { content, { ignoreCache: true, - withIncludes: true + withIncludes: true, + collectReferences: this.withReferences, } ); if (rpgDocs) { const ileObject = await this.resolvePathToObject(filePath, options.text); + if (this.withReferences && ileObject.source) { + ileObject.source.symbols = rpgleDocToSymbolList(rpgDocs); + } + this.createRpgTarget(ileObject, filePath, rpgDocs, options); } @@ -860,7 +880,8 @@ export class Targets { text: options.text, source: { relativePath, - extension + extension, + symbols: [], } } @@ -1504,7 +1525,8 @@ export class Targets { // Change the extension so it's picked up correctly during the build process. currentTarget.source = { extension: `pgm`, - relativePath: undefined + relativePath: undefined, + symbols: [] } // Store a fake path for this program object @@ -1518,7 +1540,8 @@ export class Targets { type: `MODULE`, source: { relativePath: basePath, - extension: path.extname(basePath).substring(1) + extension: path.extname(basePath).substring(1), + symbols: [] } }; From a4fd0374af7dc97d068c912c2ac60b123d3db519 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Tue, 1 Apr 2025 14:25:08 -0400 Subject: [PATCH 03/18] RPGLE references Signed-off-by: worksofliam --- cli/src/targets.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/targets.ts b/cli/src/targets.ts index 3060b3f..8b94287 100644 --- a/cli/src/targets.ts +++ b/cli/src/targets.ts @@ -49,7 +49,7 @@ export interface SourceSymbol { export interface ILEObject { systemName: string; longName?: string; - type: ObjectType; + type: ObjectType; // TODO: standardise on types text?: string, source?: { relativePath: string; From 83040dca60a71bb76c07b39dbf3d9185ad2a8c0b Mon Sep 17 00:00:00 2001 From: worksofliam Date: Tue, 1 Apr 2025 14:41:03 -0400 Subject: [PATCH 04/18] CL references Signed-off-by: worksofliam --- cli/src/languages/clle.ts | 58 +++++++++++++++++++++++++++++++++++++++ cli/src/targets.ts | 6 ++++ 2 files changed, 64 insertions(+) create mode 100644 cli/src/languages/clle.ts diff --git a/cli/src/languages/clle.ts b/cli/src/languages/clle.ts new file mode 100644 index 0000000..b9f65bc --- /dev/null +++ b/cli/src/languages/clle.ts @@ -0,0 +1,58 @@ +import { Module, Subroutine, Variable, File, Statement } from "vscode-clle/language"; +import { SourceSymbol, SymbolReferences } from "../targets"; + +export function collectClReferences(relativePath: string, doc: Module): SourceSymbol[] { + const symbols: SourceSymbol[] = []; + + const defs = doc.getDefinitions(); + + const getRefs = (def: Variable|Subroutine): SymbolReferences => { + const refs = doc.getReferences(def); + + const result: SymbolReferences = {relativePath: []}; + + for (const ref of refs) { + result[relativePath].push(ref); + } + + return result; + } + + for (const def of defs) { + let newSymbol: SourceSymbol|undefined; + if (def instanceof Variable && def.name) { + newSymbol = { + name: def.name.value, + type: String(def.dataType), // TODO: what is this? + relativePath, + references: getRefs(def) + } + } + + else if (def instanceof File && def.file) { + newSymbol = { + name: def.file.name, + type: `table`, //TODO: is table correct? + relativePath, + references: {} // File's don't have refs, but children do + } + + // TODO: get children of file + } + + else if (def instanceof Subroutine && def.name) { + newSymbol = { + name: def.name.value, + type: `subroutine`, + relativePath, + references: getRefs(def) + } + } + + if (newSymbol) { + symbols.push(newSymbol); + } + } + + return symbols; +} \ No newline at end of file diff --git a/cli/src/targets.ts b/cli/src/targets.ts index 8b94287..a118993 100644 --- a/cli/src/targets.ts +++ b/cli/src/targets.ts @@ -15,6 +15,7 @@ import { asPosix, getReferenceObjectsFrom, getSystemNameFromPath, toLocalPath } import { extCanBeProgram, getObjectType } from './builders/environment'; import { isSqlFunction } from './languages/sql'; import { ReadFileSystem } from './readFileSystem'; +import { collectClReferences } from './languages/clle'; export type ObjectType = "PGM" | "SRVPGM" | "MODULE" | "FILE" | "BNDDIR" | "DTAARA" | "CMD" | "MENU" | "DTAQ"; @@ -426,6 +427,11 @@ export class Targets { module.parseStatements(tokens); const ileObject = await this.resolvePathToObject(filePath); + + if (this.withReferences && ileObject.source) { + ileObject.source.symbols = collectClReferences(relative, module); + } + this.createClTarget(ileObject, filePath, module, options); } else if (ddsExtension.includes(ext)) { From d033c9dfc3314d24c431bbd7e9830d7561b8429e Mon Sep 17 00:00:00 2001 From: worksofliam Date: Tue, 1 Apr 2025 15:02:27 -0400 Subject: [PATCH 05/18] SQL references Signed-off-by: worksofliam --- cli/src/languages/sql.ts | 30 ++++++++++++++++++++++++++++++ cli/src/targets.ts | 17 +++++------------ cli/src/utils.ts | 10 ++++++++++ 3 files changed, 45 insertions(+), 12 deletions(-) diff --git a/cli/src/languages/sql.ts b/cli/src/languages/sql.ts index cec759c..7f898dc 100644 --- a/cli/src/languages/sql.ts +++ b/cli/src/languages/sql.ts @@ -1,5 +1,10 @@ // https://www.ibm.com/docs/en/i/7.4?topic=reference-built-in-functions +import Statement from "vscode-db2i/src/language/sql/statement"; +import { ObjectRef } from "vscode-db2i/src/language/sql/types"; +import { SourceSymbol } from "../targets"; +import { trimQuotes } from "../utils"; + const AGGREGATE_FUNCTIONS = [ "ANY_VALUE", "ARRAY_AGG", @@ -290,4 +295,29 @@ export function isSqlFunction(name: string): boolean { AGGREGATE_FUNCTIONS.includes(name) || SCALAR_FUNCTIONS.includes(name) ); +} + +export function getSymbolFromDef(relativePath: string, statement: Statement, mainDef: ObjectRef) { + const symbol: SourceSymbol = { + name: mainDef.object.system || trimQuotes(mainDef.object.name, `"`), + type: mainDef.createType || `object`, + relativePath, + references: {[relativePath]: [{ + start: mainDef.tokens[0].range.start, + end: mainDef.tokens[mainDef.tokens.length - 1].range.end + }]} + } + + const children = statement.getRoutineParameters(); + + if (children && children.length > 0) { + symbol.children = children.map(child => ({ + name: child.alias, + type: child.createType, + relativePath, + references: { [relativePath]: [child.tokens[0].range] } + })) + } + + return symbol; } \ No newline at end of file diff --git a/cli/src/targets.ts b/cli/src/targets.ts index a118993..9826675 100644 --- a/cli/src/targets.ts +++ b/cli/src/targets.ts @@ -11,9 +11,9 @@ import { rpgExtensions, clExtensions, ddsExtension, sqlExtensions, srvPgmExtensi import Parser from "vscode-rpgle/language/parser"; import { rpgleDocToSymbolList, setupParser } from './languages/rpgle'; import { Logger } from './logger'; -import { asPosix, getReferenceObjectsFrom, getSystemNameFromPath, toLocalPath } from './utils'; +import { asPosix, getReferenceObjectsFrom, getSystemNameFromPath, toLocalPath, trimQuotes } from './utils'; import { extCanBeProgram, getObjectType } from './builders/environment'; -import { isSqlFunction } from './languages/sql'; +import { getSymbolFromDef, isSqlFunction } from './languages/sql'; import { ReadFileSystem } from './readFileSystem'; import { collectClReferences } from './languages/clle'; @@ -879,6 +879,8 @@ export class Targets { const extension = pathDetail.ext.substring(1); + const symbol = this.withReferences ? getSymbolFromDef(relativePath, statement, mainDef) : undefined; + let ileObject: ILEObject = { systemName: objectName.toUpperCase(), longName: hasLongName, @@ -887,7 +889,7 @@ export class Targets { source: { relativePath, extension, - symbols: [], + symbols: symbol ? [symbol] : [], } } @@ -1705,12 +1707,3 @@ export class Targets { } } -function trimQuotes(input: string|boolean, value = `'`) { - if (typeof input === `string`) { - if (input[0] === value) input = input.substring(1); - if (input[input.length - 1] === value) input = input.substring(0, input.length - 1); - return input; - } else { - return ''; - } -} \ No newline at end of file diff --git a/cli/src/utils.ts b/cli/src/utils.ts index e4aaa2a..ab279e3 100644 --- a/cli/src/utils.ts +++ b/cli/src/utils.ts @@ -234,4 +234,14 @@ export function checkFileExists(file) { return fs.promises.access(file, fs.constants.F_OK) .then(() => true) .catch(() => false) +} + +export function trimQuotes(input: string|boolean, value = `'`) { + if (typeof input === `string`) { + if (input[0] === value) input = input.substring(1); + if (input[input.length - 1] === value) input = input.substring(0, input.length - 1); + return input; + } else { + return ''; + } } \ No newline at end of file From 7f0596638d9e49d8d5a2ad87ff3ed54e4d36c5bb Mon Sep 17 00:00:00 2001 From: worksofliam Date: Wed, 2 Apr 2025 08:05:48 -0500 Subject: [PATCH 06/18] Fix for scope checking Signed-off-by: worksofliam --- cli/src/languages/rpgle.ts | 37 ++++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/cli/src/languages/rpgle.ts b/cli/src/languages/rpgle.ts index 9b754fd..0d48878 100644 --- a/cli/src/languages/rpgle.ts +++ b/cli/src/languages/rpgle.ts @@ -79,7 +79,7 @@ export function setupParser(targets: Targets): Parser { return parser; } -export function toSymbolRefs(def: Declaration): SymbolReferences { +function toSymbolRefs(def: Declaration): SymbolReferences { if (def.references.length > 0) { let refs: SymbolReferences = {}; for (const ref of def.references) { @@ -95,8 +95,23 @@ export function toSymbolRefs(def: Declaration): SymbolReferences { return {}; } + + +function handleSubitems(def: Declaration, newSymbol: SourceSymbol) { + if (def.subItems.length > 0) { + newSymbol.children = def.subItems.map(sub => { + return { + name: sub.name, + type: sub.type, + relativePath: def.position.path, // subitems are in the same file + references: toSymbolRefs(def) + }; + }); + } +} + export function rpgleDocToSymbolList(doc: Cache): SourceSymbol[] { - let symbols: SourceSymbol[] = [] + let symbols: SourceSymbol[] = []; const allDefs = [ ...doc.constants, @@ -115,16 +130,7 @@ export function rpgleDocToSymbolList(doc: Cache): SourceSymbol[] { references: toSymbolRefs(def) } - if (def.subItems.length > 0) { - newSymbol.children = def.subItems.map(sub => { - return { - name: sub.name, - type: sub.type, - relativePath: def.position.path, // subitems are in the same file - references: toSymbolRefs(def) - }; - }); - } + handleSubitems(def, newSymbol); symbols.push(newSymbol); } @@ -137,7 +143,12 @@ export function rpgleDocToSymbolList(doc: Cache): SourceSymbol[] { references: toSymbolRefs(proc) }; - newSymbol.children = rpgleDocToSymbolList(proc.scope); + // TODO: check on parameters when there is and isn't a scope + if (proc.scope) { + newSymbol.children = rpgleDocToSymbolList(proc.scope); + } else if (proc.subItems.length > 0) { + handleSubitems(proc, newSymbol); + } symbols.push(newSymbol); } From 57f4655c0ac7b54e35c23964906acd2bbb276640 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Wed, 2 Apr 2025 08:19:00 -0500 Subject: [PATCH 07/18] DDS symbols Signed-off-by: worksofliam --- cli/src/targets.ts | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/cli/src/targets.ts b/cli/src/targets.ts index 9826675..d4a6437 100644 --- a/cli/src/targets.ts +++ b/cli/src/targets.ts @@ -45,6 +45,7 @@ export interface SourceSymbol { relativePath: string; children?: SourceSymbol[], references: SymbolReferences; + external?: string; } export interface ILEObject { @@ -610,8 +611,18 @@ export class Targets { const ddsRefKeywords = [`PFILE`, `REF`, `JFILE`]; + let symbols: SourceSymbol[] = []; + for (const recordFormat of dds.formats) { + let recordFormatSymbol: SourceSymbol = { + name: recordFormat.name, + type: `recordFormat`, + relativePath: localPath, + references: {}, + children: [] + }; + // Look through this record format keywords for the keyword we're looking for for (const keyword of ddsRefKeywords) { const keywordObj = recordFormat.keywords.find(k => k.name === keyword); @@ -633,6 +644,13 @@ export class Targets { // Then, let's loop through the fields in this format and see if we can find REFFLD for (const field of recordFormat.fields) { + let currentFieldSymbol: SourceSymbol = { + name: field.name, + type: `field`, + references: {}, + relativePath: localPath, + } + const refFld = field.keywords.find(k => k.name === `REFFLD`); if (refFld) { @@ -640,11 +658,18 @@ export class Targets { if (fileRef) { handleObjectPath(`REFFLD`, recordFormat, fileRef); + currentFieldSymbol.external = fileRef; } } + + recordFormatSymbol.children.push(currentFieldSymbol); } + + symbols.push(recordFormatSymbol); } + ileObject.source.symbols = symbols; + if (target.deps.length > 0) infoOut(`Depends on: ${target.deps.map(d => `${d.systemName}.${d.type}`).join(` `)}`); From 51728da4d79fa29871a2a90a0baa651b6c547155 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Wed, 2 Apr 2025 08:32:55 -0500 Subject: [PATCH 08/18] Change how SQL symbols are gathered Signed-off-by: worksofliam --- cli/src/languages/sql.ts | 22 +++++++++++++--------- cli/src/targets.ts | 6 ++++-- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/cli/src/languages/sql.ts b/cli/src/languages/sql.ts index 7f898dc..9abe441 100644 --- a/cli/src/languages/sql.ts +++ b/cli/src/languages/sql.ts @@ -297,7 +297,7 @@ export function isSqlFunction(name: string): boolean { ); } -export function getSymbolFromDef(relativePath: string, statement: Statement, mainDef: ObjectRef) { +export function getSymbolFromCreate(relativePath: string, statement: Statement, mainDef: ObjectRef) { const symbol: SourceSymbol = { name: mainDef.object.system || trimQuotes(mainDef.object.name, `"`), type: mainDef.createType || `object`, @@ -308,15 +308,19 @@ export function getSymbolFromDef(relativePath: string, statement: Statement, mai }]} } - const children = statement.getRoutineParameters(); + const createTypesWithFields = [`table`]; - if (children && children.length > 0) { - symbol.children = children.map(child => ({ - name: child.alias, - type: child.createType, - relativePath, - references: { [relativePath]: [child.tokens[0].range] } - })) + if (createTypesWithFields.includes(mainDef.createType.toLowerCase())) { + const children = statement.getRoutineParameters(); + + if (children && children.length > 0) { + symbol.children = children.map(child => ({ + name: child.alias, + type: child.createType, + relativePath, + references: { [relativePath]: [child.tokens[0].range] } + })) + } } return symbol; diff --git a/cli/src/targets.ts b/cli/src/targets.ts index d4a6437..583f986 100644 --- a/cli/src/targets.ts +++ b/cli/src/targets.ts @@ -13,7 +13,7 @@ import { rpgleDocToSymbolList, setupParser } from './languages/rpgle'; import { Logger } from './logger'; import { asPosix, getReferenceObjectsFrom, getSystemNameFromPath, toLocalPath, trimQuotes } from './utils'; import { extCanBeProgram, getObjectType } from './builders/environment'; -import { getSymbolFromDef, isSqlFunction } from './languages/sql'; +import { getSymbolFromCreate, isSqlFunction } from './languages/sql'; import { ReadFileSystem } from './readFileSystem'; import { collectClReferences } from './languages/clle'; @@ -904,7 +904,9 @@ export class Targets { const extension = pathDetail.ext.substring(1); - const symbol = this.withReferences ? getSymbolFromDef(relativePath, statement, mainDef) : undefined; + const symbol = this.withReferences ? getSymbolFromCreate(relativePath, statement, mainDef) : undefined; + + // TODO: consider procedure/function bodies? let ileObject: ILEObject = { systemName: objectName.toUpperCase(), From 91684f6417e709ec31308a595c2d6ffdfd789ccd Mon Sep 17 00:00:00 2001 From: worksofliam Date: Wed, 2 Apr 2025 08:47:40 -0500 Subject: [PATCH 09/18] Set external refs at target time Signed-off-by: worksofliam --- cli/src/languages/rpgle.ts | 26 +++++++++++- cli/src/targets.ts | 82 ++++++++++++++++++++------------------ 2 files changed, 67 insertions(+), 41 deletions(-) diff --git a/cli/src/languages/rpgle.ts b/cli/src/languages/rpgle.ts index 0d48878..26f744e 100644 --- a/cli/src/languages/rpgle.ts +++ b/cli/src/languages/rpgle.ts @@ -6,6 +6,7 @@ import Parser from "vscode-rpgle/language/parser"; import { SourceSymbol, SymbolReferences, Targets } from '../targets'; import Cache from "vscode-rpgle/language/models/cache"; import Declaration from 'vscode-rpgle/language/models/declaration'; +import { trimQuotes } from '../utils'; let includeFileCache: { [path: string]: string } = {}; @@ -95,8 +96,6 @@ function toSymbolRefs(def: Declaration): SymbolReferences { return {}; } - - function handleSubitems(def: Declaration, newSymbol: SourceSymbol) { if (def.subItems.length > 0) { newSymbol.children = def.subItems.map(sub => { @@ -110,6 +109,29 @@ function handleSubitems(def: Declaration, newSymbol: SourceSymbol) { } } +export function getExtPrRef(ref: Declaration, type: "EXTPROC"|"EXTPGM" = "EXTPROC"): string { + const keyword = ref.keyword; + let importName: string = ref.name; + const ext: string | boolean = keyword[type]; + if (ext) { + if (ext === true) importName = ref.name; + else importName = ext; + } + + if (importName.includes(`:`)) { + const parmParms = importName.split(`:`); + importName = parmParms.filter(p => !p.startsWith(`*`)).join(``); + } + + if (importName.startsWith(`*`)) { + importName = ref.name; + } else { + importName = trimQuotes(importName); + } + + return importName; +} + export function rpgleDocToSymbolList(doc: Cache): SourceSymbol[] { let symbols: SourceSymbol[] = []; diff --git a/cli/src/targets.ts b/cli/src/targets.ts index 583f986..32ccb46 100644 --- a/cli/src/targets.ts +++ b/cli/src/targets.ts @@ -9,13 +9,14 @@ import Document from "vscode-db2i/src/language/sql/document"; import { ObjectRef, StatementType } from 'vscode-db2i/src/language/sql/types'; import { rpgExtensions, clExtensions, ddsExtension, sqlExtensions, srvPgmExtensions, cmdExtensions } from './extensions'; import Parser from "vscode-rpgle/language/parser"; -import { rpgleDocToSymbolList, setupParser } from './languages/rpgle'; +import { getExtPrRef, rpgleDocToSymbolList, setupParser } from './languages/rpgle'; import { Logger } from './logger'; import { asPosix, getReferenceObjectsFrom, getSystemNameFromPath, toLocalPath, trimQuotes } from './utils'; import { extCanBeProgram, getObjectType } from './builders/environment'; import { getSymbolFromCreate, isSqlFunction } from './languages/sql'; import { ReadFileSystem } from './readFileSystem'; import { collectClReferences } from './languages/clle'; +import Declaration from 'vscode-rpgle/language/models/declaration'; export type ObjectType = "PGM" | "SRVPGM" | "MODULE" | "FILE" | "BNDDIR" | "DTAARA" | "CMD" | "MENU" | "DTAQ"; @@ -85,6 +86,7 @@ export interface ImpactedObject { } interface RpgLookup { + def: Declaration, lookup: string, line?: number } @@ -1042,28 +1044,7 @@ export class Targets { // define internal imports ileObject.imports = cache.procedures .filter((proc: any) => proc.keyword[`EXTPROC`]) - .map(ref => { - const keyword = ref.keyword; - let importName: string = ref.name; - const extproc: string | boolean = keyword[`EXTPROC`]; - if (extproc) { - if (extproc === true) importName = ref.name; - else importName = extproc; - } - - if (importName.includes(`:`)) { - const parmParms = importName.split(`:`); - importName = parmParms.filter(p => !p.startsWith(`*`)).join(``); - } - - if (importName.startsWith(`*`)) { - importName = ref.name; - } else { - importName = trimQuotes(importName); - } - - return importName; - }); + .map((r) => getExtPrRef(r, `EXTPROC`)); // define exported functions if (cache.keyword[`NOMAIN`]) { @@ -1075,6 +1056,13 @@ export class Targets { .map(ref => ref.name.toUpperCase()); } + const setExternal = (symbolName: string, external: string) => { + if (this.withReferences && ileObject.source && ileObject.source.symbols) { + const symbol = ileObject.source.symbols.find(s => s.name === symbolName); + if (symbol) symbol.external = external; + } + } + infoOut(`${ileObject.systemName}.${ileObject.type}: ${ileObject.source.relativePath}`); if (cache.includes && cache.includes.length > 0) { @@ -1201,14 +1189,10 @@ export class Targets { .filter((proc: any) => proc.keyword[`EXTPGM`]) .map((ref): RpgLookup => { const keyword = ref.keyword; - let fileName = ref.name; - const extpgm = keyword[`EXTPGM`]; - if (extpgm) { - if (extpgm === true) fileName = ref.name; - else fileName = trimQuotes(extpgm); - } + const fileName = getExtPrRef(ref, `EXTPGM`); return { + def: ref, lookup: fileName.toUpperCase(), line: ref.position ? ref.position.range.line : undefined }; @@ -1223,7 +1207,8 @@ export class Targets { if (resolvedObject) { // because of legacy fixed CALL, there can be dupliicate EXTPGMs with the same name :( if (!target.deps.some(d => d.systemName === resolvedObject.systemName && d.type && resolvedObject.type)) { - target.deps.push(resolvedObject) + target.deps.push(resolvedObject); + setExternal(ref.def.name, resolvedObject.systemName); } } @@ -1243,20 +1228,23 @@ export class Targets { // Find external data structure sources scope.structs - .filter((struct: any) => struct.keyword[`EXTNAME`]) + .filter((struct) => struct.keyword[`EXTNAME`]) .map((struct): RpgLookup => { const keyword = struct.keyword; const value = trimQuotes(keyword[`EXTNAME`]); return { + def: struct, lookup: value.split(`:`)[0].toUpperCase(), line: struct.position ? struct.position.range.line : undefined }; }) .forEach((ref: RpgLookup) => { const resolvedObject = this.searchForObject({ systemName: ref.lookup, type: `FILE` }); - if (resolvedObject) target.deps.push(resolvedObject) - else { + if (resolvedObject) { + target.deps.push(resolvedObject); + setExternal(ref.def.name, resolvedObject.systemName); + } else { this.logger.fileLog(ileObject.source.relativePath, { message: `No object found for reference '${ref.lookup}'`, type: `warning`, @@ -1289,6 +1277,7 @@ export class Targets { } return { + def: file, lookup: possibleName.toUpperCase(), line: file.position ? file.position.range.line : undefined }; @@ -1300,8 +1289,10 @@ export class Targets { if (previouslyScanned) return; const resolvedObject = this.searchForObject({ systemName: ref.lookup, type: `FILE` }); - if (resolvedObject) target.deps.push(resolvedObject) - else { + if (resolvedObject) { + target.deps.push(resolvedObject); + setExternal(ref.def.name, resolvedObject.systemName); + } else { this.logger.fileLog(ileObject.source.relativePath, { message: `No object found for reference '${ref.lookup}'`, type: `warning`, @@ -1314,6 +1305,7 @@ export class Targets { scope.sqlReferences .filter(ref => !ref.description) .map((ref): RpgLookup => ({ + def: ref, lookup: trimQuotes(ref.name, `"`).toUpperCase(), line: ref.position ? ref.position.range.line : undefined })) @@ -1321,8 +1313,12 @@ export class Targets { const previouslyScanned = target.deps.some((r => (ref.lookup === r.systemName || ref.lookup === r.longName?.toUpperCase()) && r.type === `FILE`)); if (previouslyScanned) return; const resolvedObject = this.searchForObject({ systemName: ref.lookup, type: `FILE` }); - if (resolvedObject) target.deps.push(resolvedObject) - else if (!isSqlFunction(ref.lookup)) { + + if (resolvedObject) { + target.deps.push(resolvedObject); + setExternal(ref.def.name, resolvedObject.systemName); + + } else if (!isSqlFunction(ref.lookup)) { this.logger.fileLog(ileObject.source.relativePath, { message: `No object found for reference '${ref.lookup}'`, type: `warning`, @@ -1344,6 +1340,7 @@ export class Targets { } return { + def: ref, lookup: fileName.toUpperCase(), line: ref.position ? ref.position.range.line : undefined }; @@ -1352,7 +1349,10 @@ export class Targets { if (ignoredObjects.includes(ref.lookup.toUpperCase())) return; const resolvedObject = this.searchForObject({ systemName: ref.lookup, type: `DTAARA` }); - if (resolvedObject) target.deps.push(resolvedObject) + if (resolvedObject) { + target.deps.push(resolvedObject); + setExternal(ref.def.name, resolvedObject.systemName); + } else { this.logger.fileLog(ileObject.source.relativePath, { message: `No object found for reference '${ref.lookup}'`, @@ -1374,13 +1374,17 @@ export class Targets { } return { + def: ref, lookup: fileName.toUpperCase(), line: ref.position ? ref.position.range.line : undefined }; }) .forEach((ref: RpgLookup) => { const resolvedObject = this.searchForObject({ systemName: ref.lookup, type: `DTAARA` }); - if (resolvedObject) target.deps.push(resolvedObject) + if (resolvedObject) { + target.deps.push(resolvedObject); + setExternal(ref.def.name, resolvedObject.systemName); + } else { this.logger.fileLog(ileObject.source.relativePath, { message: `No object found for reference '${ref.lookup}'`, From 8aea07990b50eb92b4f047f1ac0cbe79914d90e2 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Wed, 2 Apr 2025 08:49:27 -0500 Subject: [PATCH 10/18] Note for REFFLD Signed-off-by: worksofliam --- cli/src/targets.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cli/src/targets.ts b/cli/src/targets.ts index 32ccb46..56a844c 100644 --- a/cli/src/targets.ts +++ b/cli/src/targets.ts @@ -660,6 +660,8 @@ export class Targets { if (fileRef) { handleObjectPath(`REFFLD`, recordFormat, fileRef); + + // TODO: how does handleObjectPath also add an external symbol? currentFieldSymbol.external = fileRef; } } From 625da9540731af26e7aebfca8607ee3aea74f422 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Wed, 2 Apr 2025 08:57:28 -0500 Subject: [PATCH 11/18] Get relative path for RPG symbols Signed-off-by: worksofliam --- cli/src/languages/clle.ts | 9 +++++---- cli/src/languages/rpgle.ts | 16 ++++++++-------- cli/src/targets.ts | 11 +++++------ 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/cli/src/languages/clle.ts b/cli/src/languages/clle.ts index b9f65bc..2aba956 100644 --- a/cli/src/languages/clle.ts +++ b/cli/src/languages/clle.ts @@ -23,7 +23,7 @@ export function collectClReferences(relativePath: string, doc: Module): SourceSy if (def instanceof Variable && def.name) { newSymbol = { name: def.name.value, - type: String(def.dataType), // TODO: what is this? + type: String(def.dataType), // RTODO: what is this? relativePath, references: getRefs(def) } @@ -32,12 +32,13 @@ export function collectClReferences(relativePath: string, doc: Module): SourceSy else if (def instanceof File && def.file) { newSymbol = { name: def.file.name, - type: `table`, //TODO: is table correct? + type: `table`, //RTODO: is table correct? relativePath, - references: {} // File's don't have refs, but children do + references: {}, // File's don't have refs, but children do + external: def.file.name } - // TODO: get children of file + // RTODO: get children of file } else if (def instanceof Subroutine && def.name) { diff --git a/cli/src/languages/rpgle.ts b/cli/src/languages/rpgle.ts index 26f744e..c2db7c0 100644 --- a/cli/src/languages/rpgle.ts +++ b/cli/src/languages/rpgle.ts @@ -96,13 +96,13 @@ function toSymbolRefs(def: Declaration): SymbolReferences { return {}; } -function handleSubitems(def: Declaration, newSymbol: SourceSymbol) { +function handleSubitems(cwd, def: Declaration, newSymbol: SourceSymbol) { if (def.subItems.length > 0) { newSymbol.children = def.subItems.map(sub => { return { name: sub.name, type: sub.type, - relativePath: def.position.path, // subitems are in the same file + relativePath: path.relative(cwd, def.position.path), // subitems are in the same file references: toSymbolRefs(def) }; }); @@ -132,7 +132,7 @@ export function getExtPrRef(ref: Declaration, type: "EXTPROC"|"EXTPGM" = "EXTPRO return importName; } -export function rpgleDocToSymbolList(doc: Cache): SourceSymbol[] { +export function rpgleDocToSymbolList(cwd: string, doc: Cache): SourceSymbol[] { let symbols: SourceSymbol[] = []; const allDefs = [ @@ -148,11 +148,11 @@ export function rpgleDocToSymbolList(doc: Cache): SourceSymbol[] { let newSymbol: SourceSymbol = { name: def.name, type: def.type, - relativePath: def.position.path, // TODO: make sure this is relative!! + relativePath: path.relative(cwd, def.position.path), references: toSymbolRefs(def) } - handleSubitems(def, newSymbol); + handleSubitems(cwd, def, newSymbol); symbols.push(newSymbol); } @@ -161,15 +161,15 @@ export function rpgleDocToSymbolList(doc: Cache): SourceSymbol[] { let newSymbol: SourceSymbol = { name: proc.name, type: `procedure`, - relativePath: proc.position.path, // TODO: make sure this is relative!! + relativePath: path.relative(cwd, proc.position.path), references: toSymbolRefs(proc) }; // TODO: check on parameters when there is and isn't a scope if (proc.scope) { - newSymbol.children = rpgleDocToSymbolList(proc.scope); + newSymbol.children = rpgleDocToSymbolList(cwd, proc.scope); } else if (proc.subItems.length > 0) { - handleSubitems(proc, newSymbol); + handleSubitems(cwd, proc, newSymbol); } symbols.push(newSymbol); diff --git a/cli/src/targets.ts b/cli/src/targets.ts index 56a844c..b08c749 100644 --- a/cli/src/targets.ts +++ b/cli/src/targets.ts @@ -52,7 +52,7 @@ export interface SourceSymbol { export interface ILEObject { systemName: string; longName?: string; - type: ObjectType; // TODO: standardise on types + type: ObjectType; // RTODO: standardise on types text?: string, source?: { relativePath: string; @@ -122,13 +122,12 @@ export class Targets { private resolvedExports: { [name: string]: ILEObject } = {}; private targets: { [name: string]: ILEObjectTarget } = {}; private needsBinder = false; - private withReferences = true; private suggestions: TargetSuggestions = {}; public logger: Logger; - constructor(private cwd: string, private fs: ReadFileSystem) { + constructor(private cwd: string, private fs: ReadFileSystem, private withReferences = false) { this.rpgParser = setupParser(this); this.logger = new Logger(); } @@ -415,7 +414,7 @@ export class Targets { if (rpgDocs) { const ileObject = await this.resolvePathToObject(filePath, options.text); if (this.withReferences && ileObject.source) { - ileObject.source.symbols = rpgleDocToSymbolList(rpgDocs); + ileObject.source.symbols = rpgleDocToSymbolList(this.cwd, rpgDocs); } this.createRpgTarget(ileObject, filePath, rpgDocs, options); @@ -661,7 +660,7 @@ export class Targets { if (fileRef) { handleObjectPath(`REFFLD`, recordFormat, fileRef); - // TODO: how does handleObjectPath also add an external symbol? + // RTODO: how does handleObjectPath also add an external symbol? currentFieldSymbol.external = fileRef; } } @@ -910,7 +909,7 @@ export class Targets { const symbol = this.withReferences ? getSymbolFromCreate(relativePath, statement, mainDef) : undefined; - // TODO: consider procedure/function bodies? + // RTODO: consider procedure/function bodies for children symbols? let ileObject: ILEObject = { systemName: objectName.toUpperCase(), From 2b8a0b3725cb8524c9865f5c238444e99e181f8d Mon Sep 17 00:00:00 2001 From: worksofliam Date: Wed, 2 Apr 2025 09:17:08 -0500 Subject: [PATCH 12/18] First test case for project references Signed-off-by: worksofliam --- cli/src/languages/rpgle.ts | 15 +++++----- cli/src/targets.ts | 24 +++++++++------ cli/test/project_refs1.test.ts | 55 ++++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 16 deletions(-) create mode 100644 cli/test/project_refs1.test.ts diff --git a/cli/src/languages/rpgle.ts b/cli/src/languages/rpgle.ts index c2db7c0..55345dc 100644 --- a/cli/src/languages/rpgle.ts +++ b/cli/src/languages/rpgle.ts @@ -80,14 +80,15 @@ export function setupParser(targets: Targets): Parser { return parser; } -function toSymbolRefs(def: Declaration): SymbolReferences { +function toSymbolRefs(cwd: string, def: Declaration): SymbolReferences { if (def.references.length > 0) { let refs: SymbolReferences = {}; for (const ref of def.references) { - if (!refs[ref.uri]) { - refs[ref.uri] = []; + const relative = path.relative(cwd, ref.uri); + if (!refs[relative]) { + refs[relative] = []; } - refs[ref.uri].push(ref.offset); + refs[relative].push(ref.offset); } return refs; @@ -103,7 +104,7 @@ function handleSubitems(cwd, def: Declaration, newSymbol: SourceSymbol) { name: sub.name, type: sub.type, relativePath: path.relative(cwd, def.position.path), // subitems are in the same file - references: toSymbolRefs(def) + references: toSymbolRefs(cwd, def) }; }); } @@ -149,7 +150,7 @@ export function rpgleDocToSymbolList(cwd: string, doc: Cache): SourceSymbol[] { name: def.name, type: def.type, relativePath: path.relative(cwd, def.position.path), - references: toSymbolRefs(def) + references: toSymbolRefs(cwd, def) } handleSubitems(cwd, def, newSymbol); @@ -162,7 +163,7 @@ export function rpgleDocToSymbolList(cwd: string, doc: Cache): SourceSymbol[] { name: proc.name, type: `procedure`, relativePath: path.relative(cwd, proc.position.path), - references: toSymbolRefs(proc) + references: toSymbolRefs(cwd, proc) }; // TODO: check on parameters when there is and isn't a scope diff --git a/cli/src/targets.ts b/cli/src/targets.ts index b08c749..00606d9 100644 --- a/cli/src/targets.ts +++ b/cli/src/targets.ts @@ -1042,10 +1042,23 @@ export class Targets { private createRpgTarget(ileObject: ILEObject, localPath: string, cache: Cache, options: FileOptions = {}) { const pathDetail = path.parse(localPath); + + const setExternal = (symbolName: string, external: string) => { + if (this.withReferences && ileObject.source && ileObject.source.symbols) { + const symbol = ileObject.source.symbols.find(s => s.name === symbolName); + if (symbol) symbol.external = external; + } + } + // define internal imports - ileObject.imports = cache.procedures + ileObject.imports = []; + cache.procedures .filter((proc: any) => proc.keyword[`EXTPROC`]) - .map((r) => getExtPrRef(r, `EXTPROC`)); + .forEach((r) => { + const ref = getExtPrRef(r, `EXTPROC`); + ileObject.imports.push(ref); + setExternal(ref, r.name); + }); // define exported functions if (cache.keyword[`NOMAIN`]) { @@ -1057,13 +1070,6 @@ export class Targets { .map(ref => ref.name.toUpperCase()); } - const setExternal = (symbolName: string, external: string) => { - if (this.withReferences && ileObject.source && ileObject.source.symbols) { - const symbol = ileObject.source.symbols.find(s => s.name === symbolName); - if (symbol) symbol.external = external; - } - } - infoOut(`${ileObject.systemName}.${ileObject.type}: ${ileObject.source.relativePath}`); if (cache.includes && cache.includes.length > 0) { diff --git a/cli/test/project_refs1.test.ts b/cli/test/project_refs1.test.ts new file mode 100644 index 0000000..a11a1a3 --- /dev/null +++ b/cli/test/project_refs1.test.ts @@ -0,0 +1,55 @@ +import { beforeAll, describe, expect, test } from 'vitest'; + +import { Targets } from '../src/targets' +import { setupFixture } from './fixtures/projects'; +import { ReadFileSystem } from '../src/readFileSystem'; +import path from 'path'; +import { readFile } from 'fs/promises'; + +describe(`company_system tests`, () => { + const project = setupFixture(`company_system`); + + const fs = new ReadFileSystem(); + const targets: Targets = new Targets(project.cwd, fs, true); + + beforeAll(async () => { + project.setup(); + await targets.loadProject(); + + expect(targets.getTargets().length).toBeGreaterThan(0); + targets.resolveBinder(); + }); + + test('Expect symbols', async () => { + const myPgm = targets.getTarget({systemName: `MYPGM`, type: `PGM`}); + expect(myPgm).toBeDefined(); + + expect(myPgm.source).toBeDefined(); + expect(myPgm.source.symbols.length).toBe(28); + + const printf = myPgm.source.symbols.find(s => s.name === `printf`); + expect(printf).toBeDefined(); + + console.log(printf); + + expect(printf.name).toBe(`printf`); + expect(printf.relativePath).toBe(myPgm.source.relativePath); + expect(printf.external).toBe(`printf`); + expect(printf.children.length).toBe(1); + + expect(printf.references[printf.relativePath].length).toBe(2); + + // Let's check the accuracy of the references + const filePath = path.join(project.cwd, printf.relativePath); + const contents = await readFile(filePath, {encoding: `utf-8`}); + + for (const reference of printf.references[printf.relativePath]) { + const start = reference.start; + const end = reference.end; + const text = contents.substring(start, end); + + // toLowerCase because RPGLE is not case sensitive + expect(text.toLowerCase()).toBe(`printf`); + } + }); +}); \ No newline at end of file From 3611de3118e2a85b6c24b16d49d36807864720ac Mon Sep 17 00:00:00 2001 From: worksofliam Date: Wed, 2 Apr 2025 10:56:20 -0500 Subject: [PATCH 13/18] Add additional to do Signed-off-by: worksofliam --- cli/test/project_refs1.test.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cli/test/project_refs1.test.ts b/cli/test/project_refs1.test.ts index a11a1a3..aafaa41 100644 --- a/cli/test/project_refs1.test.ts +++ b/cli/test/project_refs1.test.ts @@ -51,5 +51,7 @@ describe(`company_system tests`, () => { // toLowerCase because RPGLE is not case sensitive expect(text.toLowerCase()).toBe(`printf`); } + + // RTODO: check symbols from a copybook }); }); \ No newline at end of file From 936af24a11bb0c3629caf4cf38547c950b418df9 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Wed, 2 Apr 2025 14:21:27 -0500 Subject: [PATCH 14/18] Additional test case for copy book references Signed-off-by: worksofliam --- .../fixtures/company_system/qrpglesrc/mypgm.pgm.rpgle | 1 + cli/test/project_refs1.test.ts | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/cli/test/fixtures/company_system/qrpglesrc/mypgm.pgm.rpgle b/cli/test/fixtures/company_system/qrpglesrc/mypgm.pgm.rpgle index 38fc98b..2ddaea1 100644 --- a/cli/test/fixtures/company_system/qrpglesrc/mypgm.pgm.rpgle +++ b/cli/test/fixtures/company_system/qrpglesrc/mypgm.pgm.rpgle @@ -14,5 +14,6 @@ mytext = 'Hello to all you people'; printf(mytext); dsply mytext; +dsply F01; return; \ No newline at end of file diff --git a/cli/test/project_refs1.test.ts b/cli/test/project_refs1.test.ts index aafaa41..81c3c12 100644 --- a/cli/test/project_refs1.test.ts +++ b/cli/test/project_refs1.test.ts @@ -30,8 +30,6 @@ describe(`company_system tests`, () => { const printf = myPgm.source.symbols.find(s => s.name === `printf`); expect(printf).toBeDefined(); - console.log(printf); - expect(printf.name).toBe(`printf`); expect(printf.relativePath).toBe(myPgm.source.relativePath); expect(printf.external).toBe(`printf`); @@ -52,6 +50,11 @@ describe(`company_system tests`, () => { expect(text.toLowerCase()).toBe(`printf`); } - // RTODO: check symbols from a copybook + const f1 = myPgm.source.symbols.find(s => s.name === `F01`); + expect(f1).toBeDefined(); + + expect(f1.relativePath).not.toBe(myPgm.source.relativePath); + expect(f1.references[f1.relativePath].length).toBe(1); + expect(f1.references[myPgm.source.relativePath].length).toBe(1); }); }); \ No newline at end of file From b8ea3c38a9651ed1c78f2a456eda46462e76d0dd Mon Sep 17 00:00:00 2001 From: worksofliam Date: Thu, 29 May 2025 12:05:53 -0400 Subject: [PATCH 15/18] Fix assignment issue in rules generation Signed-off-by: worksofliam --- cli/src/builders/bob.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cli/src/builders/bob.ts b/cli/src/builders/bob.ts index 849d8b5..c5cba27 100644 --- a/cli/src/builders/bob.ts +++ b/cli/src/builders/bob.ts @@ -16,12 +16,12 @@ export class BobProject { for (let target of targets.getTargets()) { let dirname: string|undefined; - if (target.source) { - const dirname = path.dirname(target.source.relativePath); + if (target.source?.relativePath) { + dirname = path.dirname(target.source.relativePath); } else if (target.type === `PGM`) { // If there is no relative path, this might mean it's a multimodule program const possibleModule = targets.getTarget({systemName: target.systemName, type: `MODULE`}); - if (possibleModule) { + if (possibleModule && possibleModule.source) { dirname = path.dirname(possibleModule.source.relativePath); } } From 72714da5e2f9176627f5deee041948034b199dd2 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Tue, 17 Jun 2025 10:25:41 -0400 Subject: [PATCH 16/18] Fix broken test cases Signed-off-by: worksofliam --- cli/package-lock.json | 4 ++-- cli/src/builders/bob.ts | 2 +- cli/src/builders/make/index.ts | 4 ++-- cli/test/cldclf.test.ts | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/cli/package-lock.json b/cli/package-lock.json index e731ebc..e1e5cc8 100644 --- a/cli/package-lock.json +++ b/cli/package-lock.json @@ -1,12 +1,12 @@ { "name": "@ibm/sourceorbit", - "version": "1.1.0", + "version": "1.1.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@ibm/sourceorbit", - "version": "1.1.0", + "version": "1.1.1", "license": "Apache 2", "dependencies": { "@modelcontextprotocol/sdk": "^1.12.1", diff --git a/cli/src/builders/bob.ts b/cli/src/builders/bob.ts index c5cba27..eb6502d 100644 --- a/cli/src/builders/bob.ts +++ b/cli/src/builders/bob.ts @@ -123,7 +123,7 @@ class RulesFile { const existingLine = this.parsed.find(r => r.target === objName && r.isUserWritten !== true); - const lineContent = `${path.relative(this.subdir, target.source.relativePath)} ${target.headers ? target.headers.join(` `) + ` ` : ``}${target.deps.filter(d => d.reference !== true).map(d => `${d.systemName}.${d.type}`).join(` `)}`.trimEnd(); + const lineContent = `${target.source?.relativePath ? path.relative(this.subdir, target.source.relativePath) : ``} ${target.headers ? target.headers.join(` `) + ` ` : ``}${target.deps.filter(d => d.reference !== true).map(d => `${d.systemName}.${d.type}`).join(` `)}`.trim(); if (existingLine) { existingLine.ogLine = `${objName}: ${lineContent}`; diff --git a/cli/src/builders/make/index.ts b/cli/src/builders/make/index.ts index e106525..ef233cb 100644 --- a/cli/src/builders/make/index.ts +++ b/cli/src/builders/make/index.ts @@ -70,7 +70,7 @@ export class MakeProject { let data = ileObject.source?.relativePath ? this.settings.getCompileDataForSource(ileObject.source.relativePath) : this.settings.getCompileDataForType(ileObject.type); const customAttributes = this.getObjectAttributes(data, ileObject); - if (ileObject.source) { + if (ileObject.source?.relativePath) { const possibleAction = this.projectActions.getActionForPath(ileObject.source.relativePath); if (possibleAction) { const clData = fromCl(possibleAction.command); @@ -319,7 +319,7 @@ export class MakeProject { const possibleTarget: ILEObjectTarget = this.targets.getTarget(ileObject) || (ileObject as ILEObjectTarget); const customAttributes = this.getObjectAttributes(data, possibleTarget); - if (ileObject.source) { + if (ileObject.source?.relativePath) { const possibleAction = this.projectActions.getActionForPath(ileObject.source.relativePath); if (possibleAction) { const clData = fromCl(possibleAction.command); diff --git a/cli/test/cldclf.test.ts b/cli/test/cldclf.test.ts index d6d7ff4..53e2a04 100644 --- a/cli/test/cldclf.test.ts +++ b/cli/test/cldclf.test.ts @@ -26,15 +26,15 @@ describe(`CL with DCLF`, () => { expect(targetObjects.length).toBe(2); - expect(targetObjects.some(t => t.systemName === `APGM` && t.type === `PGM` && t.extension === `clle`)).toBeTruthy(); - expect(targetObjects.some(t => t.systemName === `DEPARTMENT` && t.type === `FILE` && t.extension === `table`)).toBeTruthy(); + expect(targetObjects.some(t => t.systemName === `APGM` && t.type === `PGM` && t.source.extension === `clle`)).toBeTruthy(); + expect(targetObjects.some(t => t.systemName === `DEPARTMENT` && t.type === `FILE` && t.source.extension === `table`)).toBeTruthy(); }); test(`CL has valid dependency`, () => { const apgm = targets.getTarget({systemName: `APGM`, type: `PGM`}); expect(apgm).toBeDefined(); - const logs = targets.logger.getLogsFor(apgm.relativePath); + const logs = targets.logger.getLogsFor(apgm.source.relativePath); console.log(logs); expect(logs.length).toBe(0); From 0672506313580431a24fa21011fae9d59c53ba57 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Tue, 17 Jun 2025 13:53:10 -0400 Subject: [PATCH 17/18] Fetch DDS children Signed-off-by: worksofliam --- cli/src/languages/dds.ts | 449 +++++++++++++++++++++++++++++++++++++++ cli/src/targets.ts | 8 +- cli/test/ddsDeps.test.ts | 31 ++- 3 files changed, 483 insertions(+), 5 deletions(-) create mode 100644 cli/src/languages/dds.ts diff --git a/cli/src/languages/dds.ts b/cli/src/languages/dds.ts new file mode 100644 index 0000000..559f515 --- /dev/null +++ b/cli/src/languages/dds.ts @@ -0,0 +1,449 @@ + +export class DdsFile { + private formats: RecordInfo[] = []; + private currentField: FieldInfo | undefined; + private currentFields: FieldInfo[] = []; + private currentRecord: RecordInfo|undefined = new RecordInfo(`GLOBAL`); + + constructor() { } + + getFormats() { + return this.formats; + } + + /** + * @param {string[]} lines + */ + parse(lines: string[]) { + let textCounter = 0; + + let conditionals: string, name: string, len: string, type: string, dec: string, inout: string, x: string, y: string, keywords: string; + + lines.forEach((line, index) => { + line = line.padEnd(80); + + if (line[6] === `*`) { + return; + } + + conditionals = line.substring(6, 16).padEnd(10); + name = line.substring(18, 28).trim(); + const ref = line.substring(28, 29).trim().toUpperCase() === `R`; + len = line.substring(29, 34).trim(); + type = line[34].toUpperCase(); + dec = line.substring(35, 37).trim(); + inout = line[37].toUpperCase(); + y = line.substring(38, 41).trim(); + x = line.substring(41, 44).trim(); + keywords = line.substring(44).trimEnd(); + + switch (line[16]) { + case 'R': + if (this.currentField) { + this.currentField.handleKeywords(); + this.currentFields.push(this.currentField); + }; + if (this.currentRecord && this.currentFields) { + this.currentRecord.fields = this.currentFields; + } + if (this.currentRecord) { + this.currentRecord.range.end = index; + this.currentRecord.handleKeywords(); + this.formats.push(this.currentRecord); + } + + this.currentRecord = new RecordInfo(name); + this.currentRecord.range.start = index; + + this.currentFields = []; + this.currentField = undefined; + + this.HandleKeywords(keywords); + break; + + case ' ': + if ((x !== "" && y !== "") || inout === `H`) { + // From a regular display file + if (this.currentField) { + this.currentField.handleKeywords(); + this.currentFields.push(this.currentField); + } + + this.currentField = new FieldInfo(); + this.currentField.position = { + x: Number(x), + y: Number(y) + }; + + } else if (x !== "" && y === "") { + // From a printer file with no Y position + if (this.currentField) { + this.currentField.handleKeywords(); + this.currentFields.push(this.currentField); + } + + let totalX = Number(x); + if (x.startsWith(`+`)) { + totalX = this.currentFields[this.currentFields.length - 1].position.x + Number(x.substring(1)); + + if (this.currentFields[this.currentFields.length - 1] && this.currentFields[this.currentFields.length - 1].value) { + totalX += this.currentFields[this.currentFields.length - 1].value!.length; + } + } + + this.currentField = new FieldInfo(); + this.currentField.position = { + x: totalX, + y: 0 + }; + + } else if (ref) { + if (this.currentField) { + this.currentField.handleKeywords(); + this.currentFields.push(this.currentField); + } + + this.currentField = new FieldInfo(name); + this.currentField.keywords.push({name: `REFFLD`, value: name, conditions: []}) + // TODO: might be cool to do REFFLD lookups? + } + + if (name !== "") { + if (this.currentField) { + this.currentField.name = name; + this.currentField.value = ""; + this.currentField.length = Number(len); + switch (inout) { + case "I": + this.currentField.displayType = `input`; + break; + case "B": + this.currentField.displayType = `both`; + break; + case "H": + this.currentField.displayType = `hidden`; + break; + case " ": + case "O": + this.currentField.displayType = `output`; + break; + } + + this.currentField.decimals = 0; + switch (type) { + case "D": + case "Z": + case "Y": + this.currentField.type = `decimal`; + if (dec !== "") { this.currentField.decimals = Number(dec); } + break; + case `L`: //Date + this.currentField.length = 8; + this.currentField.type = `char`; + this.currentField.keywords.push({ + name: `DATE`, + value: undefined, + conditions: [] + }); + break; + case `T`: //Time + this.currentField.length = 8; + this.currentField.type = `char`; + this.currentField.keywords.push({ + name: `TIME`, + value: undefined, + conditions: [] + }); + break; + default: + this.currentField.type = `char`; + break; + } + + this.currentField.conditions.push( + ...DdsFile.parseConditionals(conditionals) + ); + + this.HandleKeywords(keywords, conditionals); + } + } + else { + if (this.currentField) { + if (!this.currentField.name) { + textCounter++; + this.currentField.name = `TEXT${textCounter}`; + if (!this.currentField.value) {this.currentField.value = "";} + this.currentField.length = this.currentField.value.length; + this.currentField.displayType = `const`; + + this.currentField.conditions.push( + ...DdsFile.parseConditionals(conditionals) + ); + } + } + this.HandleKeywords(keywords, conditionals); + } + break; + } + }); + + if (this.currentField) { + this.currentField.handleKeywords(); + this.currentFields.push(this.currentField); + }; + if (this.currentRecord) { + if (this.currentFields) { + this.currentRecord.fields = this.currentFields; + } + + this.currentRecord.range.end = lines.length; + this.currentRecord.handleKeywords(); + this.formats.push(this.currentRecord); + } + + this.currentField = undefined; + this.currentFields = []; + this.currentRecord = undefined; + } + + /** + * @param {string} keywords + * @param {string} [conditionals] + * @returns + */ + HandleKeywords(keywords: string, conditionals = ``) { + let insertIndex; + + if (this.currentField) { + insertIndex = this.currentField.keywordStrings.keywordLines.push(keywords); + this.currentField.keywordStrings.conditionalLines[insertIndex] = conditionals; + } else if (this.currentRecord) { + this.currentRecord.keywordStrings.push(keywords); + } + + + } + + static parseConditionals(conditionColumns: string): Conditional[] { + if (conditionColumns.trim() === "") {return [];} + + /** @type {Conditional[]} */ + let conditionals = []; + + //TODO: something with condition + //const condition = conditionColumns.substring(0, 1); //A (and) or O (or) + + let current = ""; + let negate = false; + let indicator = 0; + + let cIndex = 1; + + while (cIndex <= 7) { + current = conditionColumns.substring(cIndex, cIndex + 3); + + if (current.trim() !== "") { + negate = (conditionColumns.substring(cIndex, cIndex + 1) === "N"); + indicator = Number(conditionColumns.substring(cIndex + 1, cIndex + 3)); + + conditionals.push(new Conditional(indicator, negate)); + } + + cIndex += 3; + } + + return conditionals; + } + + static parseKeywords(keywordStrings: string[], conditionalStrings?: { [line: number]: string }) { + let result: { value: string, keywords: Keyword[], conditions: Conditional[] } = { + value: ``, + keywords: [], + conditions: [] + }; + + const newLineMark = `~`; + + let value = keywordStrings.join(newLineMark) + newLineMark; + let conditionalLine = 1; + + if (value.length > 0) { + value += ` `; + + let inBrackets = 0; + let word = ``; + let innerValue = ``; + let inString = false; + + for (let i = 0; i < value.length; i++) { + switch (value[i]) { + case `+`: + case `-`: + if (value[i + 1] !== newLineMark) { + innerValue += value[i]; + } + break; + + case `'`: + if (inBrackets > 0) { + innerValue += value[i]; + } else { + if (inString) { + inString = false; + + result.value = innerValue; + innerValue = ``; + } else { + inString = true; + } + } + break; + + case `(`: + if (inString) { + innerValue += value[i]; + } else { + inBrackets++; + } + break; + case `)`: + if (inString) { + innerValue += value[i]; + } else { + inBrackets--; + } + break; + + case newLineMark: + case ` `: + if (inBrackets > 0 || inString) { + if (value[i] !== newLineMark) { + innerValue += value[i]; + } + } else { + if (word.length > 0) { + let conditionals = conditionalStrings ? conditionalStrings[conditionalLine] : undefined; + + result.keywords.push({ + name: word.toUpperCase(), + value: innerValue.length > 0 ? innerValue : undefined, + conditions: conditionals ? DdsFile.parseConditionals(conditionals) : [] + }); + + word = ``; + innerValue = ``; + } + } + + if (value[i] === newLineMark) { conditionalLine += 1; } + break; + default: + if (inBrackets > 0 || inString) { innerValue += value[i]; } + else { word += value[i]; } + break; + } + } + } + + return result; + } +} + +class RecordInfo { + public fields: FieldInfo[] = []; + public range: { start: number, end: number } = { start: -1, end: -1 }; + public isWindow: boolean = false; + public windowReference: string | undefined = undefined; + public windowSize: { y: number, x: number, width: number, height: number } = { y: 0, x: 0, width: 80, height: 24 }; + public keywordStrings: string[] = []; + public keywords: Keyword[] = []; + constructor(public name: string) { } + + handleKeywords() { + const data = DdsFile.parseKeywords(this.keywordStrings); + + this.keywords.push(...data.keywords); + + this.keywords.forEach(keyword => { + switch (keyword.name) { + case "WINDOW": + this.isWindow = true; + if (keyword.value) { + let points = keyword.value.split(' '); + + if (points.length >= 3 && points[0].toUpperCase() === `*DFT`) { + // WINDOW (*DFT Y X) + this.windowSize = { + y: 2, + x: 2, + width: Number(points[2]), + height: Number(points[1]) + }; + } else { + if (points.length === 1) { + // WINDOW (REF) + this.windowReference = points[0]; + + } else if (points.length >= 4) { + // WINDOW (*DFT SY SX Y X) + this.windowSize = { + y: Number(points[0]) || 2, + x: Number(points[1]) || 2, + width: Number(points[3]), + height: Number(points[2]) + }; + } + } + + switch (points[0]) { + case `*DFT`: + break; + } + + switch (points.length) { + case 4: + //WINDOW (STARTY STARTX SIZEY SIZEX) + + break; + case 1: + //WINDOW (REF) + this.windowReference = points[0]; + break; + } + } + + break; + } + }); + } +} + +interface Keyword { name: string, value?: string, conditions: Conditional[] }; + +class FieldInfo { + public value: string | undefined; + public type: "char" | "decimal" | undefined; + public displayType: "input" | "output" | "both" | "const" | "hidden" | undefined; + public length: number = 0; + public decimals: number = 0; + public position: { x: number, y: number } = { x: 0, y: 0 }; + public keywordStrings: { keywordLines: string[], conditionalLines: { [lineIndex: number]: string } } = { keywordLines: [], conditionalLines: {} }; + public conditions: Conditional[] = []; + public keywords: Keyword[] = []; + constructor(public name?: string) { + } + + handleKeywords() { + const data = DdsFile.parseKeywords(this.keywordStrings.keywordLines, this.keywordStrings.conditionalLines); + + this.keywords.push(...data.keywords); + + if (data.value.length > 0) { + this.value = data.value; + } + } +} + +class Conditional { + constructor(public indicator: number, public negate = false) { } +} \ No newline at end of file diff --git a/cli/src/targets.ts b/cli/src/targets.ts index df3ea00..8f6eabf 100644 --- a/cli/src/targets.ts +++ b/cli/src/targets.ts @@ -4,7 +4,6 @@ import Cache from "vscode-rpgle/language/models/cache"; import { IncludeStatement } from "vscode-rpgle/language/parserTypes"; import { infoOut, warningOut } from './cli'; import { DefinitionType, File, Module, CLParser } from 'vscode-clle/language'; -import { DisplayFile as dds } from "vscode-displayfile/src/dspf"; import Document from "vscode-db2i/src/language/sql/document"; import { ObjectRef, StatementType } from 'vscode-db2i/src/language/sql/types'; import { rpgExtensions, clExtensions, ddsExtension, sqlExtensions, srvPgmExtensions, cmdExtensions } from './extensions'; @@ -17,6 +16,7 @@ import { getSymbolFromCreate, isSqlFunction } from './languages/sql'; import { ReadFileSystem } from './readFileSystem'; import { collectClReferences } from './languages/clle'; import Declaration from 'vscode-rpgle/language/models/declaration'; +import { DdsFile } from './languages/dds'; export type ObjectType = "PGM" | "SRVPGM" | "MODULE" | "FILE" | "BNDDIR" | "DTAARA" | "CMD" | "MENU" | "DTAQ"; @@ -448,7 +448,7 @@ export class Targets { this.createClTarget(ileObject, filePath, module, options); } else if (ddsExtension.includes(ext)) { - const ddsFile = new dds(); + const ddsFile = new DdsFile(); ddsFile.parse(content.split(eol)); const ileObject = await this.resolvePathToObject(filePath, options.text); @@ -570,7 +570,7 @@ export class Targets { /** * Handles all DDS types: pf, lf, dspf */ - private createDdsFileTarget(ileObject: ILEObject, localPath: string, dds: dds, options: FileOptions = {}) { + private createDdsFileTarget(ileObject: ILEObject, localPath: string, dds: DdsFile, options: FileOptions = {}) { const target: ILEObjectTarget = { ...ileObject, deps: [] @@ -625,7 +625,7 @@ export class Targets { let symbols: SourceSymbol[] = []; - for (const recordFormat of dds.formats) { + for (const recordFormat of dds.getFormats()) { let recordFormatSymbol: SourceSymbol = { name: recordFormat.name, diff --git a/cli/test/ddsDeps.test.ts b/cli/test/ddsDeps.test.ts index 25b33a2..2a35033 100644 --- a/cli/test/ddsDeps.test.ts +++ b/cli/test/ddsDeps.test.ts @@ -12,7 +12,7 @@ describe(`dds_refs tests`, () => { const project = setupFixture(`dds_deps`); const fs = new ReadFileSystem(); - const targets = new Targets(project.cwd, fs); + const targets = new Targets(project.cwd, fs, true); targets.setSuggestions({ renames: true, includes: true }) beforeAll(async () => { @@ -103,4 +103,33 @@ describe(`dds_refs tests`, () => { expect(baseRules).toContain(`PROVIDER.FILE: PROVIDER.PF`); expect(baseRules).toContain(`PROVIDE1.FILE: PROVIDE1.LF PROVIDER.FILE`); }); + + test(`DDS symbols`, () => { + 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(); + + const files = [pro250d, provider]; + + for (const f of files) { + console.log(`File: ${f.systemName}`) + expect(f.source).toBeDefined(); + expect(f.source.symbols).toBeDefined(); + expect(f.source.symbols.length).toBeGreaterThan(0); + + for (const recordFormat of f.source.symbols) { + if (recordFormat.name !== `GLOBAL`) { + console.log(`Record format: ${recordFormat.name}`); + console.log(recordFormat); + expect(recordFormat.children.length).toBeGreaterThan(0); + } + } + } + + // TODO: better test for checking keys? + }); }); \ No newline at end of file From e9825581b6af0aa8d40609b685e1882643fe2e42 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Tue, 17 Jun 2025 14:55:19 -0400 Subject: [PATCH 18/18] Export lookup Signed-off-by: worksofliam --- cli/src/targets.ts | 8 ++++++++ cli/test/project_refs1.test.ts | 37 ++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/cli/src/targets.ts b/cli/src/targets.ts index 8f6eabf..df2b96f 100644 --- a/cli/src/targets.ts +++ b/cli/src/targets.ts @@ -1684,6 +1684,14 @@ export class Targets { ); } + public resolveExport(name: string) { + const allExports = Object.keys(this.resolvedExports); + const exportName = name.toUpperCase(); + + const validName = allExports.find(e => e.toUpperCase() === exportName); + return this.resolvedExports[validName]; + } + public getExports() { return this.resolvedExports; } diff --git a/cli/test/project_refs1.test.ts b/cli/test/project_refs1.test.ts index 81c3c12..3997c5f 100644 --- a/cli/test/project_refs1.test.ts +++ b/cli/test/project_refs1.test.ts @@ -57,4 +57,41 @@ describe(`company_system tests`, () => { expect(f1.references[f1.relativePath].length).toBe(1); expect(f1.references[myPgm.source.relativePath].length).toBe(1); }); + + test('Export lookup', async () => { + const depts = targets.getTarget({systemName: `DEPTS`, type: `PGM`}); + expect(depts).toBeDefined(); + expect(depts.source).toBeDefined(); + expect(depts.source.symbols.length).toBeGreaterThan(0); + + const ClearSubfileProc = depts.source.symbols.find(s => s.name === `ClearSubfile`); + expect(ClearSubfileProc).toBeDefined(); + expect(ClearSubfileProc.external).toBeUndefined(); + expect(ClearSubfileProc.children.length).toBe(0) + + const ToLowerProc = depts.source.symbols.find(s => s.name === `ToLower`); + expect(ToLowerProc).toBeDefined(); + expect(ToLowerProc.external).toBe(`ToLower`); + expect(ToLowerProc.children.length).toBe(1); + + const exportLookup = targets.resolveExport(ToLowerProc.external); + expect(exportLookup).toBeDefined(); + + expect(exportLookup.systemName).toBe(`UTILS`); + expect(exportLookup.source.extension).toBe(`bnd`); + + const ut = targets.getTarget(exportLookup); + expect(ut).toBeDefined(); + expect(ut.deps.length).toBe(1); + + const utilsMod = ut.deps[0]; + expect(utilsMod.source).toBeDefined(); + expect(utilsMod.source.extension).toBe(`sqlrpgle`); + expect(utilsMod.source.symbols.length).toBe(1); + + const ToLower = utilsMod.source.symbols[0]; + expect(ToLower.name).toBe(`ToLower`); + expect(Object.keys(ToLower.references).length).toBe(1); + expect(ToLower.references[ToLower.relativePath].length).toBe(2); + }); }); \ No newline at end of file