Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions examples/markdown/docs/custom-objects/Account.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Account

## API Name
`ns__Account`
15 changes: 15 additions & 0 deletions examples/markdown/docs/custom-objects/Contact.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Contact

## API Name
`ns__Contact`

## Fields
### PhotoUrl

**API Name**

`ns__PhotoUrl__c`

**Type**

*Url*
4 changes: 4 additions & 0 deletions examples/markdown/docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Custom Objects

### [Account](custom-objects/Account.md)

### [Contact](custom-objects/Contact.md)

### [Event__c](custom-objects/Event__c.md)

Represents an event that people can register for.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomObject xmlns="http://soap.sforce.com/2006/04/metadata">
<actionOverrides>
<actionName>CallHighlightAction</actionName>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>CancelEdit</actionName>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>Delete</actionName>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>Edit</actionName>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>EmailHighlightAction</actionName>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>EnableCustomerPortalUser</actionName>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>List</actionName>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>ListClean</actionName>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>New</actionName>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>RequestUpdate</actionName>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>SaveEdit</actionName>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>SmsHighlightAction</actionName>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>Tab</actionName>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>View</actionName>
<content>Account_Record_Page</content>
<formFactor>Large</formFactor>
<skipRecordTypeSelect>false</skipRecordTypeSelect>
<type>Flexipage</type>
</actionOverrides>
<actionOverrides>
<actionName>ViewCustomerPortalUser</actionName>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>WebsiteHighlightAction</actionName>
<type>Default</type>
</actionOverrides>
<compactLayoutAssignment>Account_Compact_Layout</compactLayoutAssignment>
<enableEnhancedLookup>true</enableEnhancedLookup>
<enableFeeds>true</enableFeeds>
<enableHistory>false</enableHistory>
<searchLayouts>
<customTabListAdditionalFields>ACCOUNT.NAME</customTabListAdditionalFields>
<customTabListAdditionalFields>ACCOUNT.ADDRESS1_CITY</customTabListAdditionalFields>
<customTabListAdditionalFields>ACCOUNT.PHONE1</customTabListAdditionalFields>
<lookupDialogsAdditionalFields>ACCOUNT.NAME</lookupDialogsAdditionalFields>
<lookupDialogsAdditionalFields>CORE.USERS.ALIAS</lookupDialogsAdditionalFields>
<lookupDialogsAdditionalFields>ACCOUNT.TYPE</lookupDialogsAdditionalFields>
<lookupPhoneDialogsAdditionalFields>ACCOUNT.NAME</lookupPhoneDialogsAdditionalFields>
<lookupPhoneDialogsAdditionalFields>CORE.USERS.ALIAS</lookupPhoneDialogsAdditionalFields>
<lookupPhoneDialogsAdditionalFields>ACCOUNT.TYPE</lookupPhoneDialogsAdditionalFields>
<lookupPhoneDialogsAdditionalFields>ACCOUNT.PHONE1</lookupPhoneDialogsAdditionalFields>
<searchResultsAdditionalFields>ACCOUNT.NAME</searchResultsAdditionalFields>
<searchResultsAdditionalFields>ACCOUNT.PHONE1</searchResultsAdditionalFields>
<searchResultsAdditionalFields>CORE.USERS.ALIAS</searchResultsAdditionalFields>
</searchLayouts>
<sharingModel>ReadWrite</sharingModel>
</CustomObject>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomObject xmlns="http://soap.sforce.com/2006/04/metadata">
</CustomObject>
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@cparra/apexdocs",
"version": "3.4.1",
"version": "3.4.2",
"description": "Library with CLI capabilities to generate documentation for Salesforce Apex classes.",
"keywords": [
"apex",
Expand Down
11 changes: 10 additions & 1 deletion src/application/source-code-file-reader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,16 @@ export function processFiles(fileSystem: FileSystem) {
return (rootPath: string, exclude: string[]) => {
return pipe(
fileSystem.getComponents(rootPath),
(components) => components.filter((component) => !isExcluded(component.content!, exclude)),
(components) => {
return components.map((component) => {
const pathLocation = component.type.name === 'ApexClass' ? component.content : component.xml;
return {
...component,
filePath: pathLocation!,
};
});
},
(components) => components.filter((component) => !isExcluded(component.filePath, exclude)),
(components) => convertersToUse.map((converter) => converter(components)),
(bundles) => bundles.flat(),
);
Expand Down
4 changes: 2 additions & 2 deletions src/core/markdown/adapters/type-to-renderable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ function objectMetadataToRenderable(
type: 'customobject',
headingLevel: 1,
apiName: getApiName(objectMetadata.name, config),
heading: objectMetadata.label,
heading: objectMetadata.label ?? objectMetadata.name,
name: objectMetadata.name,
doc: {
description: objectMetadata.description ? [objectMetadata.description] : [],
Expand All @@ -277,7 +277,7 @@ function fieldMetadataToRenderable(
return {
type: 'field',
headingLevel: headingLevel,
heading: field.label,
heading: field.label ?? field.name,
description: field.description ? [field.description] : [],
apiName: getApiName(field.name, config),
fieldType: field.type,
Expand Down
2 changes: 2 additions & 0 deletions src/core/markdown/templates/custom-object-template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@ export const customObjectTemplate = `

\`{{{apiName}}}\`

{{#if fieldType}}
**Type**

*{{fieldType}}*
{{/if}}

{{#unless @last}}---{{/unless}}
{{/each}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,66 +137,4 @@ describe('when parsing custom field metadata', () => {

expect(E.isLeft(result)).toBe(true);
});

test('An error is returned when the CustomField key is not an object', async () => {
const unparsed: UnparsedCustomFieldBundle = {
type: 'customfield',
name: 'PhotoUrl__c',
parentName: 'MyFirstObject__c',
filePath: 'src/field/PhotoUrl__c.field-meta.xml',
content: `
<?xml version="1.0" encoding="UTF-8"?>
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">invalid</CustomField>`,
};

const result = await reflectCustomFieldSources([unparsed])();

expect(E.isLeft(result)).toBe(true);
});

test('An error is returned when the CustomKey object does not contain the label key', async () => {
const unparsed: UnparsedCustomFieldBundle = {
type: 'customfield',
name: 'PhotoUrl__c',
parentName: 'MyFirstObject__c',
filePath: 'src/field/PhotoUrl__c.field-meta.xml',
content: `
<?xml version="1.0" encoding="UTF-8"?>
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>PhotoUrl__c</fullName>
<externalId>false</externalId>
<required>false</required>
<trackFeedHistory>false</trackFeedHistory>
<type>Url</type>
<description>A Photo URL field</description>
</CustomField>`,
};

const result = await reflectCustomFieldSources([unparsed])();

expect(E.isLeft(result)).toBe(true);
});

test('An error is returned when the CustomKey object does not contain the type key', async () => {
const unparsed: UnparsedCustomFieldBundle = {
type: 'customfield',
name: 'PhotoUrl__c',
parentName: 'MyFirstObject__c',
filePath: 'src/field/PhotoUrl__c.field-meta.xml',
content: `
<?xml version="1.0" encoding="UTF-8"?>
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>PhotoUrl__c</fullName>
<externalId>false</externalId>
<label>PhotoUrl</label>
<required>false</required>
<trackFeedHistory>false</trackFeedHistory>
<description>A Photo URL field</description>
</CustomField>`,
};

const result = await reflectCustomFieldSources([unparsed])();

expect(E.isLeft(result)).toBe(true);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -156,26 +156,4 @@ describe('when parsing SObject metadata', () => {

expect(E.isLeft(result)).toBe(true);
});

test('an error is thrown when the label is missing', async () => {
const sObjectContent = `
<?xml version="1.0" encoding="UTF-8"?>
<CustomObject xmlns="http://soap.sforce.com/2006/04/metadata">
<deploymentStatus>Deployed</deploymentStatus>
<description>test object for testing</description>
<pluralLabel>MyFirstObjects</pluralLabel>
<visibility>Public</visibility>
</CustomObject>`;

const unparsed: UnparsedCustomObjectBundle = {
type: 'customobject',
name: 'MyFirstObject__c',
filePath: 'src/object/MyFirstObject__c.object-meta.xml',
content: sObjectContent,
};

const result = await reflectCustomObjectSources([unparsed])();

expect(E.isLeft(result)).toBe(true);
});
});
32 changes: 7 additions & 25 deletions src/core/reflection/sobject/reflect-custom-field-source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ export type CustomFieldMetadata = {
type_name: 'customfield';
description: string | null;
name: string;
label: string;
type: string;
label?: string | null;
type?: string | null;
parentName: string;
};

Expand Down Expand Up @@ -43,7 +43,7 @@ function reflectCustomFieldSource(
);
}

function validate(parsedResult: unknown): E.Either<Error, { CustomField: object }> {
function validate(parsedResult: unknown): E.Either<Error, { CustomField: unknown }> {
const err = E.left(new Error('Invalid custom field metadata'));

function isObject(value: unknown) {
Expand All @@ -54,34 +54,16 @@ function validate(parsedResult: unknown): E.Either<Error, { CustomField: object
return 'CustomField' in value ? E.right(value) : err;
}

function theCustomFieldKeyIsAnObject(value: Record<'CustomField', unknown>) {
return typeof value.CustomField === 'object' ? E.right(value as Record<'CustomField', object>) : err;
}

function theCustomFieldObjectContainsTheLabelKey(value: Record<'CustomField', object>) {
return 'label' in value.CustomField ? E.right(value) : err;
}

function theCustomFieldObjectContainsTheTypeKey(value: Record<'CustomField', object>) {
return 'type' in value.CustomField ? E.right(value) : err;
}

return pipe(
parsedResult,
isObject,
E.chain(hasTheCustomFieldKey),
E.chain(theCustomFieldKeyIsAnObject),
E.chain(theCustomFieldObjectContainsTheLabelKey),
E.chain(theCustomFieldObjectContainsTheTypeKey),
);
return pipe(parsedResult, isObject, E.chain(hasTheCustomFieldKey));
}

function toCustomFieldMetadata(parserResult: { CustomField: object }): CustomFieldMetadata {
function toCustomFieldMetadata(parserResult: { CustomField: unknown }): CustomFieldMetadata {
const customField = typeof parserResult.CustomField === 'object' ? parserResult.CustomField : {};
const defaultValues = {
description: null,
};

return { ...defaultValues, ...parserResult.CustomField, type_name: 'customfield' } as CustomFieldMetadata;
return { ...defaultValues, ...customField, type_name: 'customfield' } as CustomFieldMetadata;
}

function addName(metadata: CustomFieldMetadata, name: string): CustomFieldMetadata {
Expand Down
26 changes: 7 additions & 19 deletions src/core/reflection/sobject/reflect-custom-object-sources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export type ObjectMetadata = {
type_name: 'customobject';
deploymentStatus: string;
visibility: string;
label: string;
label?: string | null;
name: string;
description: string | null;
fields: ParsedFile<CustomFieldMetadata>[];
Expand Down Expand Up @@ -45,7 +45,7 @@ function reflectCustomObjectSource(
);
}

function validate(parseResult: unknown): E.Either<Error, { CustomObject: object }> {
function validate(parseResult: unknown): E.Either<Error, { CustomObject: unknown }> {
const err = E.left(new Error('Invalid SObject metadata'));

function isObject(value: unknown) {
Expand All @@ -56,31 +56,19 @@ function validate(parseResult: unknown): E.Either<Error, { CustomObject: object
return 'CustomObject' in value ? E.right(value) : err;
}

function theCustomObjectKeyIsAnObject(value: Record<'CustomObject', unknown>) {
return typeof value.CustomObject === 'object' ? E.right(value as Record<'CustomObject', object>) : err;
}

function theCustomObjectContainsTheLabelKey(value: Record<'CustomObject', object>) {
return 'label' in value.CustomObject ? E.right(value) : err;
}

return pipe(
parseResult,
isObject,
E.chain(hasTheCustomObjectKey),
E.chain(theCustomObjectKeyIsAnObject),
E.chain(theCustomObjectContainsTheLabelKey),
);
return pipe(parseResult, isObject, E.chain(hasTheCustomObjectKey));
}

function toObjectMetadata(parserResult: { CustomObject: object }): ObjectMetadata {
function toObjectMetadata(parserResult: { CustomObject: unknown }): ObjectMetadata {
const customObject = typeof parserResult.CustomObject === 'object' ? parserResult.CustomObject : {};

const defaultValues = {
deploymentStatus: 'Deployed',
visibility: 'Public',
description: null,
fields: [] as ParsedFile<CustomFieldMetadata>[],
};
return { ...defaultValues, ...parserResult.CustomObject } as ObjectMetadata;
return { ...defaultValues, ...customObject } as ObjectMetadata;
}

function addName(objectMetadata: ObjectMetadata, name: string): ObjectMetadata {
Expand Down
Loading
Loading