codefori / codefori/vscode-ibmi

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

Open
#3,249 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
418
Forks
160
Avg merge
4d 19h
Merged PRs (30d)
12

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:**
```rpgle
/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:

```typescript
// 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:

```typescript
// 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:

```bash
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):

```typescript
// 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:
```bash
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):

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

This produces:
```bash
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:
```rpgle
/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.ts` — `sanitizeObjNamesForPase`, `qualifyPath`, `escapePath`
* `src/api/IBMiContent.ts` — `memberResolve`

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.