Skip to content

Commit c098a8a

Browse files
mshanemcpeternhale
andauthored
Sm/strictFolders-match-suffixes (#477)
* fix: strictDir is pickier about its matches * refactor: filter instead of continue * Update src/resolve/metadataResolver.ts Co-authored-by: peternhale <[email protected]> * refactor: pr feedback from pete * test: mock repo typos * test: the mock registry should be valid, too * style: no manual debugs * chore: linter changes * chore: remove comments Co-authored-by: peternhale <[email protected]>
1 parent 6a13e58 commit c098a8a

File tree

4 files changed

+189
-16
lines changed

4 files changed

+189
-16
lines changed

src/resolve/metadataResolver.ts

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -148,22 +148,38 @@ export class MetadataResolver {
148148
return !!parseMetadataXml(fsPath);
149149
}
150150

151-
private resolveType(fsPath: string): MetadataType | undefined {
152-
let resolvedType: MetadataType;
151+
private resolveTypeFromStrictFolder(fsPath: string): MetadataType | undefined {
152+
const pathParts = fsPath.split(sep);
153+
// first, filter out types that don't appear in the path
154+
// then iterate using for/of to allow for early break
155+
return this.registry
156+
.getStrictFolderTypes()
157+
.filter(
158+
(type) =>
159+
// the type's directory is in the path, AND
160+
pathParts.includes(type.directoryName) &&
161+
// types with folders only have folder components living at the top level.
162+
// if the fsPath is a folder component, let a future strategy deal with it
163+
(!type.inFolder || parentName(fsPath) !== type.directoryName)
164+
)
165+
.find(
166+
(type) =>
167+
// any of the following 3 options is considered a good match
168+
// mixedContent and bundles don't have a suffix to match
169+
['mixedContent', 'bundle'].includes(type.strategies?.adapter) ||
170+
// the suffix matches the type we think it is
171+
(type.suffix && fsPath.endsWith(`${type.suffix}`)) ||
172+
// the type has children and the path also includes THAT directory
173+
(type.children?.types &&
174+
Object.values(type.children?.types)
175+
.map((childType) => childType.directoryName)
176+
.some((dirName) => pathParts.includes(dirName)))
177+
);
178+
}
153179

180+
private resolveType(fsPath: string): MetadataType | undefined {
154181
// attempt 1 - check if the file is part of a component that requires a strict type folder
155-
const pathParts = new Set(fsPath.split(sep));
156-
for (const type of this.registry.getStrictFolderTypes()) {
157-
if (pathParts.has(type.directoryName)) {
158-
// types with folders only have folder components living at the top level.
159-
// if the fsPath is a folder component, let a future strategy deal with it
160-
// const isFolderType = this.getTypeFromName(typeId).inFolder;
161-
if (!type.inFolder || parentName(fsPath) !== type.directoryName) {
162-
resolvedType = type;
163-
}
164-
break;
165-
}
166-
}
182+
let resolvedType = this.resolveTypeFromStrictFolder(fsPath);
167183

168184
// attempt 2 - check if it's a metadata xml file
169185
if (!resolvedType) {

test/mock/registry/mockRegistry.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -284,11 +284,15 @@ export const mockRegistryData = {
284284
mixedSingleFile: 'mixedcontentsinglefile',
285285
dtl: 'decomposedtoplevel',
286286
ms: 'missingstrategies',
287+
nestedParent: 'nestedparent',
288+
nestedChild: 'nestedchild',
289+
document: 'document',
290+
nondecomposed: 'nondecomposedchild',
287291
},
288292
strictDirectoryNames: {
289-
mixedContentDirectories: 'mixedcontentdirectory',
293+
mixedcontentdirectories: 'mixedcontentdirectory',
290294
bundles: 'bundle',
291-
decomposed: 'decomposed',
295+
decomposeds: 'decomposed',
292296
mixedSingleFiles: 'mixedcontentsinglefile',
293297
mixedContentInFolders: 'mixedcontentinfolder',
294298
decomposedTopLevels: 'decomposedtoplevel',
@@ -298,6 +302,7 @@ export const mockRegistryData = {
298302
y: 'decomposed',
299303
g: 'decomposedtoplevel',
300304
badchildtype: 'mixedcontentsinglefile',
305+
nondecomposedchild: 'nondecomposedparent',
301306
},
302307
apiVersion: '48.0',
303308
};
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*
2+
* Copyright (c) 2021, salesforce.com, inc.
3+
* All rights reserved.
4+
* Licensed under the BSD 3-Clause license.
5+
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
6+
*/
7+
import { expect } from 'chai';
8+
import { MetadataRegistry } from '../../src';
9+
import { mockRegistryData as defaultRegistry } from '../mock/registry/mockRegistry';
10+
import { MetadataType } from '../../src/registry/types';
11+
12+
describe('Mock Registry Validation', () => {
13+
const registry = defaultRegistry as MetadataRegistry;
14+
const typesWithChildren = Object.values(registry.types).filter((type) => type.children);
15+
16+
describe('every child type has an entry in children', () => {
17+
const childMapping = new Map<string, string>();
18+
19+
typesWithChildren.map((parentType) =>
20+
Object.values(parentType.children.types).map((childType) => {
21+
childMapping.set(childType.id, parentType.id);
22+
})
23+
);
24+
25+
childMapping.forEach((parentId, childId) => {
26+
it(`has a childType for ${childId} : ${parentId}`, () => {
27+
expect(parentId).to.be.a('string');
28+
expect(childId).to.be.a('string');
29+
expect(registry.childTypes[childId]).to.equal(parentId);
30+
});
31+
});
32+
});
33+
34+
describe('suffix object is complete', () => {
35+
const knownExceptions: string[] = [];
36+
37+
const suffixMap = new Map<string, string>();
38+
Object.values(registry.types)
39+
.filter((type) => type.suffix && !type.strictDirectoryName && !knownExceptions.includes(type.name))
40+
.map((type) => {
41+
// mapping for the type's suffix
42+
suffixMap.set(type.suffix, type.id);
43+
if (type.children) {
44+
Object.values(type.children.types).map((childType) => {
45+
// mapping for the child's suffix
46+
suffixMap.set(childType.suffix, childType.id);
47+
});
48+
}
49+
});
50+
suffixMap.forEach((typeid, suffix) => {
51+
it(`has a suffix entry for "${suffix}" : "${typeid}"`, () => {
52+
expect(typeid).to.be.a('string');
53+
expect(suffix).to.be.a('string');
54+
expect(registry.suffixes[suffix]).to.equal(typeid);
55+
});
56+
});
57+
});
58+
59+
describe('suffixes must be unique on non-strict types', () => {
60+
const suffixMap = new Map<string, MetadataType[]>();
61+
Object.values(registry.types).map((type) => {
62+
if (type.suffix) {
63+
// some bundle types have no suffix
64+
suffixMap.set(type.suffix, suffixMap.has(type.suffix) ? [...suffixMap.get(type.suffix), type] : [type]);
65+
}
66+
});
67+
suffixMap.forEach((types, suffix) => {
68+
if (types.length > 1) {
69+
// identify when a single suffix is used in multiple metadata types.
70+
// when the happens, there can only be one who is not marked as strictDirectoryName
71+
it(`Suffix "${suffix}" used by types (${types
72+
.map((type) => type.name)
73+
.join(',')}) should have only 1 non-strict directory`, () => {
74+
const nonStrictTypes = types.filter((type) => !type.strictDirectoryName);
75+
expect(nonStrictTypes.length, nonStrictTypes.map((type) => type.name).join(',')).lessThan(2);
76+
});
77+
}
78+
});
79+
});
80+
81+
describe('strict types are all in strictDirectoryNames', () => {
82+
const strictTypeMap = new Map<string, string>();
83+
Object.values(registry.types)
84+
.filter((type) => type.strictDirectoryName)
85+
.map((type) => {
86+
// the type's suffix
87+
strictTypeMap.set(type.directoryName, type.id);
88+
});
89+
strictTypeMap.forEach((typeid, strictDirectoryName) => {
90+
it(`has a strictDirectoryNames entry for "${strictDirectoryName}" : "${typeid}"`, () => {
91+
expect(typeid).to.be.a('string');
92+
expect(strictDirectoryName).to.be.a('string');
93+
expect(registry.strictDirectoryNames[strictDirectoryName]).to.equal(typeid);
94+
});
95+
});
96+
});
97+
});
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright (c) 2020, salesforce.com, inc.
3+
* All rights reserved.
4+
* Licensed under the BSD 3-Clause license.
5+
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
6+
*/
7+
8+
import { join } from 'path';
9+
import { expect } from 'chai';
10+
11+
import { MetadataResolver, SourceComponent, VirtualTreeContainer } from '../../src/resolve';
12+
import { META_XML_SUFFIX } from '../../src/common';
13+
import { registry, RegistryAccess } from '../../src';
14+
15+
describe('MetadataResolver', () => {
16+
const registryAccess = new RegistryAccess(registry);
17+
describe('Should not resolve using strictDir when suffixes do not match', () => {
18+
const type = registryAccess.getTypeByName('ApexClass');
19+
const COMPONENT_NAMES = ['myClass'];
20+
// real scenario: classes/foo/objects/myCls.cls (where objects is the strictDir of another type)
21+
const TYPE_DIRECTORY = join('classes', 'subfolder', 'subfolder2', 'objects');
22+
const XML_NAMES = COMPONENT_NAMES.map((name) => `${name}.${type.suffix}${META_XML_SUFFIX}`);
23+
const XML_PATHS = XML_NAMES.map((name) => join(TYPE_DIRECTORY, name));
24+
const CONTENT_NAMES = COMPONENT_NAMES.map((name) => `${name}.${type.suffix}`);
25+
const CONTENT_PATHS = CONTENT_NAMES.map((name) => join(TYPE_DIRECTORY, name));
26+
const TREE = new VirtualTreeContainer([
27+
{
28+
dirPath: TYPE_DIRECTORY,
29+
children: XML_NAMES.concat(CONTENT_NAMES),
30+
},
31+
]);
32+
const COMPONENTS = COMPONENT_NAMES.map(
33+
(name, index) =>
34+
new SourceComponent(
35+
{
36+
name,
37+
type,
38+
xml: XML_PATHS[index],
39+
content: CONTENT_PATHS[index],
40+
},
41+
TREE
42+
)
43+
);
44+
it('metadata file', () => {
45+
const resolver = new MetadataResolver(registryAccess, TREE);
46+
const sourceComponent = resolver.getComponentsFromPath(XML_PATHS[0])[0];
47+
expect(sourceComponent.type).to.deep.equal(type);
48+
expect(sourceComponent).to.deep.equal(COMPONENTS[0]);
49+
});
50+
it('content file', () => {
51+
const resolver = new MetadataResolver(registryAccess, TREE);
52+
expect(resolver.getComponentsFromPath(CONTENT_PATHS[0])).to.deep.equal([COMPONENTS[0]]);
53+
});
54+
});
55+
});

0 commit comments

Comments
 (0)