Skip to content

Copybook members whose name starts with $ are not found ("not found" hover tooltip) #3249

@schweigerAle-strabag

Description

@schweigerAle-strabag

Bug: Copybook members whose name starts with $ are not found ("not found" hover tooltip)

Summary

When a /copy or /include directive references a source member whose name begins with the IBM i variant character $ (dollar sign), the RPGLE language server shows the tooltip "(not found)" and does not resolve the member — even though the member exists on the system.

Example:

/copy qcpysrc,$ADCONST    ← shown as "(not found)"
/copy qcpysrc,$RPGPSDS    ← shown as "(not found)"
/copy qcpysrc,AA0100S     ← found correctly

Environment

Item Version
Extension halcyontechltd.code-for-ibmi v3.0.7
RPGLE extension halcyontechltd.vscode-rpgle v0.33.4
VS Code 1.x
IBM i OS V7Rx

Root Cause

File: src/api/Tools.ts
Function: sanitizeObjNamesForPase

The function sanitizes IBM i object/member names for use inside PASE shell commands.
Currently it only handles names beginning with # (another IBM i variant character) by wrapping them in double quotes:

// Current (buggy) implementation
export function sanitizeObjNamesForPase(names: string[]): string[] {
  return names.map(name => name.startsWith('#') ? `"${name}"` : name);
}

Names starting with $ pass through unchanged.


How the bug manifests

IBMiContent.memberResolve() calls Tools.qualifyPath() with noEscape = true, which relies entirely on sanitizeObjNamesForPase for shell-safety:

// src/api/IBMiContent.ts — memberResolve()
const pathList = files
  .map(file => Tools.qualifyPath(
    inAmerican(file.library),
    inAmerican(file.name),
    inAmerican(member),   // e.g. "$ADCONST"
    asp,
    true                  // noEscape = true → no escapePath() fallback!
  ))
  .join(` `)
  .toUpperCase();

const command = `for f in ${pathList}; do if [ -f $f ]; then echo $f; break; fi; done`;

For member $ADCONST (library MYLIB, source file QCPYSRC) the generated shell command is:

for f in /QSYS.LIB/MYLIB.LIB/QCPYSRC.FILE/$ADCONST.MBR; do if [ -f $f ]; then echo $f; break; fi; done

The PASE shell expands $ADCONST as a shell variable.
Because $ADCONST is undefined, it expands to an empty string.
The path becomes /QSYS.LIB/MYLIB.LIB/QCPYSRC.FILE/.MBR, which does not exist.
memberResolve returns undefined → the language server shows "not found".

Why does # work but $ does not?
# has no special meaning inside double-quoted shell strings, so "#MEMBER" expands correctly to #MEMBER.
$ is special inside double-quoted strings — the shell still performs variable expansion.
Single quotes or a backslash escape is required to suppress $ expansion.


Suggested Fix

File: src/api/Tools.ts

Extend sanitizeObjNamesForPase to also protect names starting with $
using a backslash escape (which works correctly in an unquoted shell context):

// Suggested fix
export function sanitizeObjNamesForPase(names: string[]): string[] {
  return names.map(name => {
    if (name.startsWith('#')) return `"${name}"`;         // existing behaviour
    if (name.startsWith('$')) return `\\${name}`;         // NEW: escape $ for PASE shell
    return name;
  });
}

This produces:

for f in /QSYS.LIB/MYLIB.LIB/QCPYSRC.FILE/\$ADCONST.MBR; do ...

The shell treats \$ as a literal dollar sign, so the path resolves correctly.

Alternatively, use single quotes around the component
(single quotes prevent all shell expansion and are safe since IBM i member names cannot contain single quotes):

if (name.startsWith('$')) return `'${name}'`;

This produces:

for f in /QSYS.LIB/MYLIB.LIB/QCPYSRC.FILE/'$ADCONST'.MBR; do ...

Both approaches are equivalent; the backslash variant is slightly more consistent with how Tools.escapePath() already handles $.


Additional notes

  • The same sanitizeObjNamesForPase gap likely affects @-prefixed member names (the third IBM i variant character), though @ is not special in POSIX shell, so it does not currently cause a visible failure.
  • objectResolve() in IBMiContent.ts constructs its shell path by calling sysNameInAmerican() directly — without going through sanitizeObjNamesForPase — so it would also fail for $-prefixed object names. That may be worth a follow-up fix.
  • Tools.escapePath() already escapes $ correctly (see src/api/Tools.ts), but it is bypassed here because noEscape = true.

Steps to Reproduce

  1. Open an RPGLE source member on IBM i that contains:
    /copy qcpysrc,$ADCONST
  2. Hover over the directive.
  3. Expected: tooltip shows the resolved member path, e.g. `MYLIB/QCPYSRC/$ADCONST` (found)
  4. Actual: tooltip shows qcpysrc,$ADCONST (not found)

References

  • src/api/Tools.tssanitizeObjNamesForPase, qualifyPath, escapePath
  • src/api/IBMiContent.tsmemberResolve

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No fields configured for Bug.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions