Skip to content

Commit 300d561

Browse files
authored
Merge pull request #2649 from codefori/fix/searchCommaMembers
Fixed member search path parsing
2 parents 88aaf3b + 864cea3 commit 300d561

File tree

2 files changed

+81
-40
lines changed

2 files changed

+81
-40
lines changed

src/api/Search.ts

+33-35
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,43 @@
1-
import * as path from 'path';
21
import { GetMemberInfo } from './components/getMemberInfo';
3-
import { IBMiMember, SearchHit, SearchResults } from './types';
4-
import { Tools } from './Tools';
52
import IBMi from './IBMi';
3+
import { Tools } from './Tools';
4+
import { IBMiMember, SearchHit, SearchResults } from './types';
65

76
export namespace Search {
8-
export async function searchMembers(connection: IBMi, library: string, sourceFile: string, searchTerm: string, members: string|IBMiMember[], readOnly?: boolean,): Promise<SearchResults> {
7+
8+
function parseHitPath(hit: SearchHit): IBMiMember {
9+
const parts = hit.path.split('/');
10+
if (parts.length == 4) {
11+
parts.shift();
12+
}
13+
return {
14+
library: parts[0],
15+
file: parts[1],
16+
name: parts[2],
17+
extension: ''
18+
};
19+
}
20+
21+
export async function searchMembers(connection: IBMi, library: string, sourceFile: string, searchTerm: string, members: string | IBMiMember[], readOnly?: boolean,): Promise<SearchResults> {
922
const config = connection.getConfig();
1023
const content = connection.getContent();
1124

1225
if (connection && config && content) {
13-
let detailedMembers: IBMiMember[]|undefined;
14-
let memberFilter: string|undefined;
26+
let detailedMembers: IBMiMember[] | undefined;
27+
let memberFilter: string | undefined;
1528

1629
if (typeof members === `string`) {
1730
memberFilter = connection.sysNameInAmerican(`${members}.MBR`);
1831
} else
19-
if (Array.isArray(members)) {
20-
if (members.length > connection.maximumArgsLength) {
21-
detailedMembers = members;
22-
memberFilter = "*.MBR";
23-
} else {
24-
memberFilter = members.map(member => `${member.name}.MBR`).join(` `);
32+
if (Array.isArray(members)) {
33+
if (members.length > connection.maximumArgsLength) {
34+
detailedMembers = members;
35+
memberFilter = "*.MBR";
36+
} else {
37+
memberFilter = members.map(member => `${member.name}.MBR`).join(` `);
38+
}
2539
}
26-
}
27-
40+
2841
// First, let's fetch the ASP info
2942
const asp = await connection.lookupLibraryIAsp(library);
3043

@@ -43,38 +56,23 @@ export namespace Search {
4356
if (detailedMembers) {
4457
// If the user provided a list of members, we need to filter the results to only include those members
4558
hits = hits.filter(hit => {
46-
const { name, dir } = path.parse(hit.path);
47-
const [lib, spf] = dir.split(`/`);
48-
return detailedMembers!.some(member => member.name === name && member.library === lib && member.file === spf);
59+
const hitMember = parseHitPath(hit);
60+
return detailedMembers!.some(member => member.name === hitMember.name && member.library === hitMember.library && member.file === hitMember.file);
4961
});
5062

5163
} else {
5264
// Else, we need to fetch the member info for each hit so we can display the correct extension
5365
const infoComponent = connection?.getComponent<GetMemberInfo>(GetMemberInfo.ID);
54-
const memberInfos: IBMiMember[] = hits.map(hit => {
55-
const { name, dir } = path.parse(hit.path);
56-
const [library, file] = dir.split(`/`);
57-
58-
return {
59-
name,
60-
library,
61-
file,
62-
extension: ``
63-
};
64-
});
65-
66-
detailedMembers = await infoComponent?.getMultipleMemberInfo(connection, memberInfos);
66+
detailedMembers = await infoComponent?.getMultipleMemberInfo(connection, hits.map(parseHitPath));
6767
}
6868

6969
// Then fix the extensions in the hit
7070
for (const hit of hits) {
71-
const { name, dir } = path.parse(hit.path);
72-
const [lib, spf] = dir.split(`/`);
73-
74-
const foundMember = detailedMembers?.find(member => member.name === name && member.library === lib && member.file === spf);
71+
const hitMember = parseHitPath(hit);
72+
const foundMember = detailedMembers?.find(member => member.name === hitMember.name && member.library === hitMember.library && member.file === hitMember.file);
7573

7674
if (foundMember) {
77-
hit.path = connection.sysNameInLocal(`${asp ? `${asp}/` : ``}${lib}/${spf}/${name}.${foundMember.extension}`);
75+
hit.path = connection.sysNameInLocal(`${asp ? `${asp}/` : ``}${foundMember.library}/${foundMember.file}/${foundMember.name}.${foundMember.extension}`);
7876
}
7977
}
8078

src/api/tests/suites/search.test.ts

+48-5
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
import { describe, it, expect, afterAll, beforeAll } from 'vitest';
1+
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
22
import { parseFilter } from '../../Filter';
3-
import { Search } from '../../Search';
43
import IBMi from '../../IBMi';
5-
import { newConnection, disposeConnection, CONNECTION_TIMEOUT } from '../connection';
4+
import { Search } from '../../Search';
5+
import { Tools } from '../../Tools';
6+
import { SearchResults } from '../../types';
7+
import { CONNECTION_TIMEOUT, disposeConnection, newConnection } from '../connection';
68

7-
describe('Search Tests', {concurrent: true}, () => {
9+
describe('Search Tests', { concurrent: true }, () => {
810
let connection: IBMi
911
beforeAll(async () => {
1012
connection = await newConnection();
@@ -53,5 +55,46 @@ describe('Search Tests', {concurrent: true}, () => {
5355
expect(result.hits.length).toBe(6);
5456
expect(checkNames(result.hits.map(hit => hit.path.split("/").at(-1)!))).toBe(true);
5557
expect(result.hits.every(hit => !hit.path.endsWith(`MBR`))).toBe(true);
56-
});
58+
}),
59+
60+
it('Member with `.` in name search', async () => {
61+
const library = connection.getConfig().tempLibrary;
62+
const file = `ZZ${Tools.makeid(6)}`;
63+
const crtsrcpf = await connection.runCommand({ command: `CRTSRCPF FILE(${library}/${file}) RCDLEN(112)`, noLibList: true });
64+
if (crtsrcpf.code !== 0) {
65+
throw new Error(`Failed to create test source file: ${crtsrcpf.stderr}`);
66+
}
67+
try {
68+
const members = [
69+
{ name: "AN.RPGLE", type: "RPGLE", content: ["Some random text", "nobody will read", "but that's for testing"] },
70+
{ name: "A.CLLE", type: "CLLE", content: ["More random text", "testing is fun", "or so they say"] },
71+
{ name: "A.CMD", type: "CMD", content: ["This is not valid for a command", "this is for a test", "so I guess it's fine"] }
72+
];
73+
74+
for (const member of members) {
75+
const addpfm = await connection.runCommand({ command: `ADDPFM FILE(${library}/${file}) MBR(${member.name}) SRCTYPE(${member.type})`, noLibList: true });
76+
if (addpfm.code !== 0) {
77+
throw new Error(`Failed to add test member: ${addpfm.stderr}`);
78+
}
79+
await connection.getContent().uploadMemberContent(library, file, member.name, member.content.join("\n"));
80+
}
81+
82+
const hasMember = (results: SearchResults, member: string) => results.hits.map(hit => hit.path.split('/').pop()).includes(member);
83+
84+
const searchTest = await Search.searchMembers(connection, library, file, "test", '*');
85+
expect(searchTest.hits.length).toBe(3);
86+
expect(hasMember(searchTest, "AN.RPGLE")).toBe(true);
87+
expect(hasMember(searchTest, "A.CLLE")).toBe(true);
88+
expect(hasMember(searchTest, "A.CMD")).toBe(true);
89+
90+
const searchTesting = await Search.searchMembers(connection, library, file, "testing", '*');
91+
expect(searchTesting.hits.length).toBe(2);
92+
expect(hasMember(searchTesting, "AN.RPGLE")).toBe(true);
93+
expect(hasMember(searchTesting, "A.CLLE")).toBe(true);
94+
expect(hasMember(searchTesting, "A.CMD")).toBe(false);
95+
}
96+
finally {
97+
await connection.runCommand({ command: `DLTF FILE(${library} / ${file})`, noLibList: true });
98+
}
99+
});
57100
});

0 commit comments

Comments
 (0)