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 d69e8bc..eb6502d 100644 --- a/cli/src/builders/bob.ts +++ b/cli/src/builders/bob.ts @@ -16,13 +16,13 @@ export class BobProject { for (let target of targets.getTargets()) { let dirname: string|undefined; - if (target.relativePath) { - dirname = path.dirname(target.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) { - dirname = path.dirname(possibleModule.relativePath); + if (possibleModule && possibleModule.source) { + dirname = path.dirname(possibleModule.source.relativePath); } } @@ -55,7 +55,7 @@ export class BobProject { if (this.targets.binderRequired()) { const servicePrograms = this.targets.getResolvedObjects(`SRVPGM`); - const relativePaths = servicePrograms.map(sp => path.dirname(sp.relativePath)); + const relativePaths = servicePrograms.map(sp => path.dirname(sp.source.relativePath)); if (relativePaths.length === 1) { // We know the rules @@ -123,7 +123,7 @@ class RulesFile { const existingLine = this.parsed.find(r => r.target === objName && r.isUserWritten !== true); - const lineContent = `${target.relativePath ? 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 = `${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/imd.ts b/cli/src/builders/imd.ts index e60e2c7..105b59d 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 677520e..ef233cb 100644 --- a/cli/src/builders/make/index.ts +++ b/cli/src/builders/make/index.ts @@ -67,11 +67,11 @@ export class MakeProject { }; const addStep = (ileObject: ILEObject) => { - let data = ileObject.relativePath ? this.settings.getCompileDataForSource(ileObject.relativePath) : this.settings.getCompileDataForType(ileObject.type); + let data = ileObject.source?.relativePath ? this.settings.getCompileDataForSource(ileObject.source.relativePath) : this.settings.getCompileDataForType(ileObject.type); const customAttributes = this.getObjectAttributes(data, ileObject); - if (ileObject.relativePath) { - const possibleAction = this.projectActions.getActionForPath(ileObject.relativePath); + if (ileObject.source?.relativePath) { + const possibleAction = this.projectActions.getActionForPath(ileObject.source.relativePath); if (possibleAction) { const clData = fromCl(possibleAction.command); // If there is an action for this object, we want to apply the action's parameters @@ -102,7 +102,7 @@ export class MakeProject { steps.push( { object: {name: ileObject.systemName, type: ileObject.type}, - command: MakeProject.resolveCommand(`CPYFRMSTMF FROMSTMF('${asPosix(ileObject.relativePath)}') TOMBR('/QSYS.LIB/&CURLIB.LIB/${qsysTempName}.FILE/${data.parameters[`srcmbr`]}.MBR') MBROPT(*REPLACE)`, ileObject, commandOptions) + command: MakeProject.resolveCommand(`CPYFRMSTMF FROMSTMF('${asPosix(ileObject.source.relativePath)}') TOMBR('/QSYS.LIB/&CURLIB.LIB/${qsysTempName}.FILE/${data.parameters[`srcmbr`]}.MBR') MBROPT(*REPLACE)`, ileObject, commandOptions) } ); } @@ -111,7 +111,7 @@ export class MakeProject { steps.push({ object: {name: ileObject.systemName, type: ileObject.type}, - relativePath: ileObject.relativePath, + relativePath: ileObject.source?.relativePath, command }); @@ -119,7 +119,7 @@ export class MakeProject { for (const postCommand of data.postCommands) { steps.push({ object: {name: ileObject.systemName, type: ileObject.type}, - relativePath: ileObject.relativePath, + relativePath: ileObject.source?.relativePath, command: MakeProject.resolveCommand(MakeProject.stripSystem(postCommand), ileObject, commandOptions) }); } @@ -150,9 +150,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 @@ -280,8 +280,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) { @@ -293,13 +293,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(); } } @@ -319,8 +319,8 @@ export class MakeProject { const possibleTarget: ILEObjectTarget = this.targets.getTarget(ileObject) || (ileObject as ILEObjectTarget); const customAttributes = this.getObjectAttributes(data, possibleTarget); - if (ileObject.relativePath) { - const possibleAction = this.projectActions.getActionForPath(ileObject.relativePath); + if (ileObject.source?.relativePath) { + const possibleAction = this.projectActions.getActionForPath(ileObject.source.relativePath); if (possibleAction) { const clData = fromCl(possibleAction.command); // If there is an action for this object, we want to apply the action's parameters @@ -399,12 +399,12 @@ export class MakeProject { const objectKey = `${ileObject.systemName}.${ileObject.type}`; 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(${data.parameters[`srcfile`]}) RCDLEN(112) CCSID(${sourceFileCcsid})"`, - `\tsystem "CPYFRMSTMF FROMSTMF('${asPosix(ileObject.relativePath)}') TOMBR('$(PREPATH)/${qsysTempName}.FILE/${data.parameters[`srcmbr`]}.MBR') MBROPT(*REPLACE)"` + `\tsystem "CPYFRMSTMF FROMSTMF('${asPosix(ileObject.source.relativePath)}') TOMBR('$(PREPATH)/${qsysTempName}.FILE/${data.parameters[`srcmbr`]}.MBR') MBROPT(*REPLACE)"` ] : []), ...(data.preCommands ? data.preCommands.map(cmd => `\t${MakeProject.resolveCommand(cmd, ileObject)}`) : []), ...(data.command ? @@ -446,7 +446,7 @@ export class MakeProject { command = command.replace(new RegExp(`\\*CURLIB`, `g`), libraryValue); 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); // Additionally, we have to support Actions variables @@ -456,9 +456,9 @@ export class MakeProject { command = simpleReplace(command, `&LIBLS`, ``); command = simpleReplace(command, `&BRANCHLIB`, libraryValue); - const pathDetail = path.parse(ileObject.relativePath || ``); + const pathDetail = path.parse(ileObject.source?.relativePath || ``); - command = simpleReplace(command, `&RELATIVEPATH`, asPosix(ileObject.relativePath)); + command = simpleReplace(command, `&RELATIVEPATH`, asPosix(ileObject.source?.relativePath)); command = simpleReplace(command, `&BASENAME`, pathDetail.base); command = simpleReplace(command, `{filename}`, pathDetail.base); command = simpleReplace(command, `&NAME`, getTrueBasename(pathDetail.name)); diff --git a/cli/src/index.ts b/cli/src/index.ts index d8778d6..270abb2 100644 --- a/cli/src/index.ts +++ b/cli/src/index.ts @@ -277,7 +277,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/languages/clle.ts b/cli/src/languages/clle.ts new file mode 100644 index 0000000..2aba956 --- /dev/null +++ b/cli/src/languages/clle.ts @@ -0,0 +1,59 @@ +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), // RTODO: what is this? + relativePath, + references: getRefs(def) + } + } + + else if (def instanceof File && def.file) { + newSymbol = { + name: def.file.name, + type: `table`, //RTODO: is table correct? + relativePath, + references: {}, // File's don't have refs, but children do + external: def.file.name + } + + // RTODO: 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/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/languages/rpgle.ts b/cli/src/languages/rpgle.ts index c2a3b62..55345dc 100644 --- a/cli/src/languages/rpgle.ts +++ b/cli/src/languages/rpgle.ts @@ -3,7 +3,10 @@ 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'; +import { trimQuotes } from '../utils'; let includeFileCache: { [path: string]: string } = {}; @@ -75,4 +78,103 @@ export function setupParser(targets: Targets): Parser { }); return parser; +} + +function toSymbolRefs(cwd: string, def: Declaration): SymbolReferences { + if (def.references.length > 0) { + let refs: SymbolReferences = {}; + for (const ref of def.references) { + const relative = path.relative(cwd, ref.uri); + if (!refs[relative]) { + refs[relative] = []; + } + refs[relative].push(ref.offset); + } + + return refs; + } + + return {}; +} + +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: path.relative(cwd, def.position.path), // subitems are in the same file + references: toSymbolRefs(cwd, def) + }; + }); + } +} + +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(cwd: string, 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: path.relative(cwd, def.position.path), + references: toSymbolRefs(cwd, def) + } + + handleSubitems(cwd, def, newSymbol); + + symbols.push(newSymbol); + } + + for (const proc of doc.procedures) { + let newSymbol: SourceSymbol = { + name: proc.name, + type: `procedure`, + relativePath: path.relative(cwd, proc.position.path), + references: toSymbolRefs(cwd, proc) + }; + + // TODO: check on parameters when there is and isn't a scope + if (proc.scope) { + newSymbol.children = rpgleDocToSymbolList(cwd, proc.scope); + } else if (proc.subItems.length > 0) { + handleSubitems(cwd, proc, newSymbol); + } + + symbols.push(newSymbol); + } + + return symbols; } \ No newline at end of file diff --git a/cli/src/languages/sql.ts b/cli/src/languages/sql.ts index cec759c..9abe441 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,33 @@ export function isSqlFunction(name: string): boolean { AGGREGATE_FUNCTIONS.includes(name) || SCALAR_FUNCTIONS.includes(name) ); +} + +export function getSymbolFromCreate(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 createTypesWithFields = [`table`]; + + 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; } \ No newline at end of file diff --git a/cli/src/targets.ts b/cli/src/targets.ts index 831d3d1..df2b96f 100644 --- a/cli/src/targets.ts +++ b/cli/src/targets.ts @@ -4,17 +4,19 @@ 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'; import Parser from "vscode-rpgle/language/parser"; -import { setupParser } from './languages/rpgle'; +import { getExtPrRef, 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 { 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"; @@ -32,15 +34,31 @@ const sqlTypeExtension = { const DEFAULT_BINDER_TARGET: 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; + external?: string; +} export interface ILEObject { systemName: string; longName?: string; - type: ObjectType; + type: ObjectType; // RTODO: standardise on types text?: string, - relativePath?: string; - extension?: string; + source?: { + relativePath: string; + extension: string; + symbols: SourceSymbol[]; + } reference?: boolean; @@ -68,6 +86,7 @@ export interface ImpactedObject { } interface RpgLookup { + def: Declaration, lookup: string, line?: number } @@ -110,7 +129,7 @@ export class Targets { 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(); } @@ -172,8 +191,11 @@ export class Targets { systemName: name, type: type, text: newText, - relativePath, - extension + source: { + relativePath, + extension, + symbols: [], + } }; // 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 @@ -254,14 +276,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.` }) @@ -275,8 +297,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; @@ -395,12 +417,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(this.cwd, rpgDocs); + } + this.createRpgTarget(ileObject, filePath, rpgDocs, options); } @@ -413,10 +440,15 @@ 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)) { - const ddsFile = new dds(); + const ddsFile = new DdsFile(); ddsFile.parse(content.split(eol)); const ileObject = await this.resolvePathToObject(filePath, options.text); @@ -473,11 +505,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: { @@ -488,8 +520,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`, }); } @@ -512,7 +544,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: { @@ -538,13 +570,13 @@ 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: [] }; - 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. @@ -571,14 +603,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 @@ -591,7 +623,17 @@ export class Targets { const ddsRefKeywords = [`PFILE`, `REF`, `JFILE`]; - for (const recordFormat of dds.formats) { + let symbols: SourceSymbol[] = []; + + for (const recordFormat of dds.getFormats()) { + + 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) { @@ -614,6 +656,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) { @@ -621,11 +670,20 @@ export class Targets { if (fileRef) { handleObjectPath(`REFFLD`, recordFormat, fileRef); + + // RTODO: how does handleObjectPath also add an external symbol? + 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(` `)}`); @@ -639,11 +697,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: { @@ -654,8 +712,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`, }); } @@ -663,7 +721,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: { @@ -674,7 +732,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`, }); @@ -693,7 +751,7 @@ export class Targets { } 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, @@ -708,7 +766,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, @@ -739,7 +797,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, @@ -749,7 +807,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, @@ -839,7 +897,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)) { @@ -864,13 +922,20 @@ export class Targets { const extension = pathDetail.ext.substring(1); + const symbol = this.withReferences ? getSymbolFromCreate(relativePath, statement, mainDef) : undefined; + + // RTODO: consider procedure/function bodies for children symbols? + let ileObject: ILEObject = { systemName: objectName.toUpperCase(), longName: hasLongName, type: this.getObjectType(relativePath, mainDef.createType), text: options.text, - relativePath, - extension + source: { + relativePath, + extension, + symbols: symbol ? [symbol] : [], + } } let suggestRename = false; @@ -890,7 +955,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: { @@ -907,7 +972,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: @@ -927,7 +992,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: { @@ -992,30 +1057,22 @@ export class Targets { private createRpgTarget(ileObject: ILEObject, localPath: string, cache: Cache, options: FileOptions = {}) { const pathDetail = path.parse(localPath); - // define internal imports - ileObject.imports = cache.procedures - .filter((proc: any) => proc.keyword[`EXTPROC`] && !proc.keyword[`EXPORT`]) - .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); - } + 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; + } + } - return importName; + // define internal imports + ileObject.imports = []; + cache.procedures + .filter((proc: any) => proc.keyword[`EXTPROC`] && !proc.keyword[`EXPORT`]) + .forEach((r) => { + const ref = getExtPrRef(r, `EXTPROC`); + ileObject.imports.push(ref); + setExternal(ref, r.name); }); // define exported functions @@ -1028,7 +1085,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 = []; @@ -1074,7 +1131,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, @@ -1083,7 +1140,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, @@ -1102,7 +1159,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: { @@ -1113,7 +1170,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`, }); @@ -1124,7 +1181,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: { @@ -1135,7 +1192,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`, }); @@ -1143,7 +1200,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`, }); @@ -1154,14 +1211,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 }; @@ -1176,12 +1229,13 @@ 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); } } 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 @@ -1196,21 +1250,24 @@ 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 { - this.logger.fileLog(ileObject.relativePath, { + 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`, line: ref.line @@ -1234,7 +1291,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`, }); @@ -1242,6 +1299,7 @@ export class Targets { } return { + def: file, lookup: possibleName.toUpperCase(), line: file.position ? file.position.range.line : undefined }; @@ -1253,9 +1311,11 @@ export class Targets { if (previouslyScanned) return; const resolvedObject = this.searchForObject({ systemName: ref.lookup, type: `FILE` }); - if (resolvedObject) target.deps.push(resolvedObject) - else { - this.logger.fileLog(ileObject.relativePath, { + 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`, line: ref.line @@ -1267,6 +1327,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 })) @@ -1274,9 +1335,13 @@ 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)) { - this.logger.fileLog(ileObject.relativePath, { + + 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`, line: ref.line @@ -1297,6 +1362,7 @@ export class Targets { } return { + def: ref, lookup: fileName.toUpperCase(), line: ref.position ? ref.position.range.line : undefined }; @@ -1305,9 +1371,12 @@ 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.relativePath, { + this.logger.fileLog(ileObject.source.relativePath, { message: `No object found for reference '${ref.lookup}'`, type: `warning`, line: ref.line @@ -1327,15 +1396,19 @@ 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.relativePath, { + this.logger.fileLog(ileObject.source.relativePath, { message: `No object found for reference '${ref.lookup}'`, type: `warning`, line: ref.line @@ -1409,8 +1482,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` }); @@ -1498,7 +1571,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` }); @@ -1507,12 +1580,15 @@ 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, + symbols: [] + } // Store a fake path for this program object this.storeResolved(path.join(this.cwd, `${currentTarget.systemName}.PGM`), currentTarget); @@ -1524,8 +1600,11 @@ export class Targets { exports: [], headers: currentTarget.headers, type: `MODULE`, - relativePath: basePath, - extension: path.extname(basePath).substring(1) + source: { + relativePath: basePath, + extension: path.extname(basePath).substring(1), + symbols: [] + } }; // Replace the old resolved object with the module @@ -1600,11 +1679,19 @@ 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) ); } + 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; } @@ -1686,12 +1773,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 66baaab..77e8146 100644 --- a/cli/src/utils.ts +++ b/cli/src/utils.ts @@ -262,4 +262,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 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/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); diff --git a/cli/test/ddsDeps.test.ts b/cli/test/ddsDeps.test.ts index 777f307..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 () => { @@ -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(); }); @@ -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 diff --git a/cli/test/ddsDepsWithRefFile.test.ts b/cli/test/ddsDepsWithRefFile.test.ts index 33c41e3..17869f2 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/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/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 b041858..f86390b 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); @@ -90,28 +90,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`); @@ -119,24 +119,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`); }); @@ -146,18 +146,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'`); @@ -380,7 +380,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`); @@ -395,7 +395,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`); @@ -432,7 +432,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`); @@ -442,7 +442,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); expect(logs.length).toBe(0); // expect(logs[0].message).toBe(`Extension should be based on type. Suggested name is 'getTotalSalary.sqludf'`); // expect(logs[0].type).toBe(`warning`); diff --git a/cli/test/project2.test.ts b/cli/test/project2.test.ts index c296e7b..6283ab4 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'`); @@ -373,7 +373,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`); @@ -388,7 +388,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`); @@ -425,7 +425,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`); @@ -435,7 +435,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); expect(logs.length).toBe(0); // expect(logs[0].message).toBe(`Extension should be based on type. Suggested name is 'getTotalSalary.sqludf'`); // expect(logs[0].type).toBe(`warning`); diff --git a/cli/test/project_refs1.test.ts b/cli/test/project_refs1.test.ts new file mode 100644 index 0000000..3997c5f --- /dev/null +++ b/cli/test/project_refs1.test.ts @@ -0,0 +1,97 @@ +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(); + + 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`); + } + + 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); + }); + + 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 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);