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

Open Beginner friendly
#3,249 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
72/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Quiet
Tech stack
typescript
Domain
tooling

Research direction

Read sanitizeObjNamesForPase in src/api/Tools.ts, then trace its use through qualifyPath and memberResolve in src/api/IBMiContent.ts; compare the existing handling with escapePath. Verify that dollar-prefixed member names remain literal in the generated PASE path and that the RPGLE /copy or /include hover resolves them instead of showing "not found".

Written by the indexing model from the issue text.

Description

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
Dominant language
TypeScript
Stars
419
Forks
160
Avg merge
2d 2h
Merged PRs (30d)
11

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from codefori/vscode-ibmi

All issues in codefori/vscode-ibmi

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.