@cparra/apexdocs 3.0.0-alpha.1 → 3.0.0-alpha.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/cli/generate.js +511 -293
  2. package/dist/{defaults-jLXD2y8-.js → defaults-DGKfeZq-.js} +1 -1
  3. package/dist/index.d.ts +23 -7
  4. package/dist/index.js +1 -1
  5. package/examples/markdown/docs/miscellaneous/MultiInheritanceClass.md +1 -1
  6. package/examples/markdown/docs/miscellaneous/SampleInterface.md +12 -8
  7. package/examples/markdown/docs/miscellaneous/Url.md +3 -3
  8. package/examples/markdown/force-app/classes/SampleInterface.cls +4 -0
  9. package/examples/vitepress/apexdocs.config.ts +1 -1
  10. package/examples/vitepress/docs/index.md +10 -10
  11. package/examples/vitepress/docs/miscellaneous/BaseClass.md +1 -1
  12. package/examples/vitepress/docs/miscellaneous/MultiInheritanceClass.md +2 -2
  13. package/examples/vitepress/docs/miscellaneous/SampleException.md +1 -1
  14. package/examples/vitepress/docs/miscellaneous/SampleInterface.md +6 -6
  15. package/examples/vitepress/docs/miscellaneous/Url.md +3 -3
  16. package/examples/vitepress/docs/sample-enums/SampleEnum.md +3 -3
  17. package/examples/vitepress/docs/samplegroup/SampleClass.md +4 -4
  18. package/package.json +2 -3
  19. package/src/application/Apexdocs.ts +53 -10
  20. package/src/application/__tests__/apex-file-reader.spec.ts +25 -25
  21. package/src/application/apex-file-reader.ts +32 -19
  22. package/src/application/file-system.ts +46 -10
  23. package/src/application/file-writer.ts +37 -15
  24. package/src/application/generators/markdown.ts +18 -31
  25. package/src/application/generators/openapi.ts +12 -8
  26. package/src/cli/commands/markdown.ts +4 -3
  27. package/src/core/markdown/__test__/generating-class-docs.spec.ts +3 -5
  28. package/src/core/markdown/__test__/generating-enum-docs.spec.ts +3 -3
  29. package/src/core/markdown/__test__/generating-interface-docs.spec.ts +5 -5
  30. package/src/core/markdown/__test__/generating-reference-guide.spec.ts +3 -3
  31. package/src/core/markdown/__test__/test-helpers.ts +1 -1
  32. package/src/core/markdown/adapters/__tests__/interface-adapter.spec.ts +1 -1
  33. package/src/core/markdown/adapters/__tests__/link-generator.spec.ts +130 -0
  34. package/src/core/markdown/adapters/documentables.ts +0 -1
  35. package/src/core/markdown/adapters/generate-link.ts +82 -0
  36. package/src/core/markdown/adapters/reference-guide.ts +3 -1
  37. package/src/core/markdown/adapters/renderable-bundle.ts +5 -22
  38. package/src/core/markdown/adapters/renderable-to-page-data.ts +2 -2
  39. package/src/core/markdown/generate-docs.ts +9 -13
  40. package/src/core/markdown/reflection/reflect-source.ts +109 -31
  41. package/src/core/openapi/open-api-docs-processor.ts +1 -1
  42. package/src/core/openapi/openapi-type-file.ts +1 -1
  43. package/src/core/openapi/parser.ts +1 -15
  44. package/src/core/parse-apex-metadata.ts +21 -5
  45. package/src/core/shared/types.d.ts +22 -6
  46. package/src/defaults.ts +1 -1
  47. package/src/index.ts +1 -6
  48. package/src/util/logger.ts +8 -21
  49. package/dist/defaults-DUwru49Q.js +0 -12
  50. package/dist/defaults-SH0Rsi5E.js +0 -11
  51. package/src/core/markdown/reflection/error-handling.ts +0 -37
@@ -10,30 +10,43 @@ export class ApexFileReader {
10
10
  /**
11
11
  * Reads from .cls files and returns their raw body.
12
12
  */
13
- static processFiles(fileSystem: FileSystem, rootPath: string, includeMetadata: boolean): UnparsedSourceFile[] {
14
- let bundles: UnparsedSourceFile[] = [];
13
+ static async processFiles(
14
+ fileSystem: FileSystem,
15
+ rootPath: string,
16
+ includeMetadata: boolean,
17
+ ): Promise<UnparsedSourceFile[]> {
18
+ const filePaths = await this.getFilePaths(fileSystem, rootPath);
19
+ const apexFilePaths = filePaths.filter((filePath) => this.isApexFile(filePath));
20
+ const filePromises = apexFilePaths.map((filePath) => this.processFile(fileSystem, filePath, includeMetadata));
21
+ return Promise.all(filePromises);
22
+ }
15
23
 
16
- const directoryContents = fileSystem.readDirectory(rootPath);
17
- directoryContents.forEach((filePath) => {
24
+ private static async getFilePaths(fileSystem: FileSystem, rootPath: string): Promise<string[]> {
25
+ const directoryContents = await fileSystem.readDirectory(rootPath);
26
+ const paths: string[] = [];
27
+ for (const filePath of directoryContents) {
18
28
  const currentPath = fileSystem.joinPath(rootPath, filePath);
19
- if (fileSystem.isDirectory(currentPath)) {
20
- bundles = bundles.concat(this.processFiles(fileSystem, currentPath, includeMetadata));
21
- }
22
-
23
- if (!this.isApexFile(filePath)) {
24
- return;
29
+ if (await fileSystem.isDirectory(currentPath)) {
30
+ paths.push(...(await this.getFilePaths(fileSystem, currentPath)));
25
31
  }
32
+ paths.push(currentPath);
33
+ }
34
+ return paths;
35
+ }
26
36
 
27
- const rawTypeContent = fileSystem.readFile(currentPath);
28
- const metadataPath = fileSystem.joinPath(rootPath, `${filePath}-meta.xml`);
29
- let rawMetadataContent = null;
30
- if (includeMetadata) {
31
- rawMetadataContent = fileSystem.exists(metadataPath) ? fileSystem.readFile(metadataPath) : null;
32
- }
37
+ private static async processFile(
38
+ fileSystem: FileSystem,
39
+ filePath: string,
40
+ includeMetadata: boolean,
41
+ ): Promise<UnparsedSourceFile> {
42
+ const rawTypeContent = await fileSystem.readFile(filePath);
43
+ const metadataPath = `${filePath}-meta.xml`;
44
+ let rawMetadataContent = null;
45
+ if (includeMetadata) {
46
+ rawMetadataContent = fileSystem.exists(metadataPath) ? await fileSystem.readFile(metadataPath) : null;
47
+ }
33
48
 
34
- bundles.push({ filePath: currentPath, content: rawTypeContent, metadataContent: rawMetadataContent });
35
- });
36
- return bundles;
49
+ return { filePath, content: rawTypeContent, metadataContent: rawMetadataContent };
37
50
  }
38
51
 
39
52
  private static isApexFile(currentFile: string): boolean {
@@ -2,25 +2,61 @@ import * as fs from 'fs';
2
2
  import * as path from 'path';
3
3
 
4
4
  export interface FileSystem {
5
- readDirectory: (sourceDirectory: string) => string[];
6
- isDirectory: (path: string) => boolean;
7
- readFile: (path: string) => string;
5
+ isDirectory: (path: string) => Promise<boolean>;
6
+ readDirectory: (sourceDirectory: string) => Promise<string[]>;
7
+ readFile: (path: string) => Promise<string>;
8
8
  joinPath: (...paths: string[]) => string;
9
9
  exists: (path: string) => boolean;
10
10
  }
11
11
 
12
+ function stat(path: string): Promise<fs.Stats> {
13
+ return new Promise((resolve, reject) => {
14
+ fs.stat(path, (err, stats) => {
15
+ if (err) {
16
+ reject(err);
17
+ } else {
18
+ resolve(stats);
19
+ }
20
+ });
21
+ });
22
+ }
23
+
24
+ function readdir(path: string): Promise<string[]> {
25
+ return new Promise((resolve, reject) => {
26
+ fs.readdir(path, (err, files) => {
27
+ if (err) {
28
+ reject(err);
29
+ } else {
30
+ resolve(files);
31
+ }
32
+ });
33
+ });
34
+ }
35
+
36
+ function readFile(path: string): Promise<string> {
37
+ return new Promise((resolve, reject) => {
38
+ fs.readFile(path, (err, data) => {
39
+ if (err) {
40
+ reject(err);
41
+ } else {
42
+ resolve(data.toString());
43
+ }
44
+ });
45
+ });
46
+ }
47
+
12
48
  export class DefaultFileSystem implements FileSystem {
13
- isDirectory(pathToRead: string): boolean {
14
- return fs.statSync(pathToRead).isDirectory();
49
+ async isDirectory(pathToRead: string): Promise<boolean> {
50
+ const stats = await stat(pathToRead);
51
+ return stats.isDirectory();
15
52
  }
16
53
 
17
- readDirectory(sourceDirectory: string): string[] {
18
- return fs.readdirSync(sourceDirectory);
54
+ readDirectory(sourceDirectory: string): Promise<string[]> {
55
+ return readdir(sourceDirectory);
19
56
  }
20
57
 
21
- readFile(pathToRead: string): string {
22
- const rawFile = fs.readFileSync(pathToRead);
23
- return rawFile.toString();
58
+ readFile(pathToRead: string): Promise<string> {
59
+ return readFile(pathToRead);
24
60
  }
25
61
 
26
62
  joinPath(...paths: string[]): string {
@@ -1,21 +1,43 @@
1
1
  import * as fs from 'fs';
2
2
  import * as path from 'path';
3
+ import * as TE from 'fp-ts/lib/TaskEither';
3
4
  import { PageData } from '../core/shared/types';
5
+ import { pipe } from 'fp-ts/function';
4
6
 
5
- export class FileWriter {
6
- static write(files: PageData[], outputDir: string, onWriteCallback: (file: PageData) => void) {
7
- files.forEach((file) => {
8
- const { filePath, content } = this.getTargetLocation(file, outputDir);
9
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
10
- fs.writeFileSync(filePath, content, 'utf8');
11
- onWriteCallback(file);
12
- });
13
- }
7
+ const mkdir: (path: fs.PathLike, options: fs.MakeDirectoryOptions) => TE.TaskEither<NodeJS.ErrnoException, void> =
8
+ TE.taskify(fs.mkdir);
14
9
 
15
- private static getTargetLocation(file: PageData, outputDir: string): PageData {
16
- return {
17
- ...file,
18
- filePath: path.join(outputDir, file.filePath),
19
- };
20
- }
10
+ const writeFile: (
11
+ path: fs.PathOrFileDescriptor,
12
+ data: string,
13
+ options?: fs.WriteFileOptions,
14
+ ) => TE.TaskEither<NodeJS.ErrnoException, void> = TE.taskify(fs.writeFile);
15
+
16
+ export function writeFiles(files: PageData[], outputDir: string, onWriteCallback?: (file: PageData) => void) {
17
+ return pipe(
18
+ files,
19
+ TE.traverseArray((file) => writeSingle(file, outputDir, onWriteCallback)),
20
+ );
21
+ }
22
+
23
+ function writeSingle(file: PageData, outputDir: string, onWriteCallback?: (file: PageData) => void) {
24
+ const ensureDirectoryExists = ({ outputDocPath }: PageData) =>
25
+ mkdir(path.dirname(outputDocPath), { recursive: true });
26
+
27
+ const writeContents = (file: PageData) => writeFile(file.outputDocPath, file.content, 'utf8');
28
+
29
+ return pipe(
30
+ resolveTargetLocation(file, outputDir),
31
+ (file) => TE.right(file),
32
+ TE.tapIO(ensureDirectoryExists),
33
+ TE.flatMap(writeContents),
34
+ TE.map(() => onWriteCallback?.(file)),
35
+ );
36
+ }
37
+
38
+ function resolveTargetLocation(file: PageData, outputDir: string): PageData {
39
+ return {
40
+ ...file,
41
+ outputDocPath: path.join(outputDir, file.outputDocPath),
42
+ };
21
43
  }
@@ -1,6 +1,4 @@
1
1
  import { generateDocs } from '../../core/markdown/generate-docs';
2
- import { FileWriter } from '../file-writer';
3
- import { Logger } from '#utils/logger';
4
2
  import { pipe } from 'fp-ts/function';
5
3
  import {
6
4
  PageData,
@@ -8,30 +6,25 @@ import {
8
6
  UnparsedSourceFile,
9
7
  UserDefinedMarkdownConfig,
10
8
  } from '../../core/shared/types';
11
- import { ReflectionError } from '../../core/markdown/reflection/error-handling';
12
9
  import { referenceGuideTemplate } from '../../core/markdown/templates/reference-guide';
13
10
  import * as TE from 'fp-ts/TaskEither';
14
11
  import { isSkip } from '../../core/shared/utils';
12
+ import { writeFiles } from '../file-writer';
13
+
14
+ class FileWritingError {
15
+ readonly _tag = 'FileWritingError';
16
+
17
+ constructor(
18
+ public message: string,
19
+ public error: unknown,
20
+ ) {}
21
+ }
15
22
 
16
23
  export default function generate(bundles: UnparsedSourceFile[], config: UserDefinedMarkdownConfig) {
17
24
  return pipe(
18
25
  generateDocumentationBundle(bundles, config),
19
- TE.map((files) => writeFilesToSystem(files, config.targetDir)),
20
- TE.mapLeft((error) => {
21
- if (error._tag === 'HookError') {
22
- Logger.error('Error(s) occurred while processing hooks. Please review the following issues:');
23
- Logger.error(error.error);
24
- return;
25
- }
26
-
27
- const errorMessages = [
28
- 'Error(s) occurred while parsing files. Please review the following issues:',
29
- ...error.errors.map(formatReflectionError),
30
- ].join('\n');
31
-
32
- Logger.error(errorMessages);
33
- }),
34
- )();
26
+ TE.flatMap((files) => writeFilesToSystem(files, config.targetDir)),
27
+ );
35
28
  }
36
29
 
37
30
  function generateDocumentationBundle(bundles: UnparsedSourceFile[], config: UserDefinedMarkdownConfig) {
@@ -42,17 +35,11 @@ function generateDocumentationBundle(bundles: UnparsedSourceFile[], config: User
42
35
  }
43
36
 
44
37
  function writeFilesToSystem(files: PostHookDocumentationBundle, outputDir: string) {
45
- FileWriter.write(
46
- [files.referenceGuide, ...files.docs]
47
- // Filter out any files that should be skipped
48
- .filter((file) => !isSkip(file)) as PageData[],
49
- outputDir,
50
- (file: PageData) => {
51
- Logger.logSingle(`${file.filePath} processed.`, false, 'green', false);
52
- },
38
+ return pipe(
39
+ [files.referenceGuide, ...files.docs].filter((file) => !isSkip(file)),
40
+ (files) => writeFiles(files as PageData[], outputDir),
41
+ TE.mapLeft((error) => {
42
+ return new FileWritingError('An error occurred while writing files to the system.', error);
43
+ }),
53
44
  );
54
45
  }
55
-
56
- function formatReflectionError(error: ReflectionError) {
57
- return `Source file: ${error.file}\n${error.message}\n`;
58
- }
@@ -2,13 +2,15 @@ import { createManifest } from '../../core/openapi/manifest-factory';
2
2
  import { RawBodyParser } from '../../core/openapi/parser';
3
3
  import { TypesRepository } from '../../core/openapi/types-repository';
4
4
  import Transpiler from '../../core/openapi/transpiler';
5
- import { FileWriter } from '../file-writer';
6
5
  import { Logger } from '#utils/logger';
7
6
  import ErrorLogger from '#utils/error-logger';
8
7
  import { reflect, ReflectionResult } from '@cparra/apex-reflection';
9
8
  import Manifest from '../../core/manifest';
10
9
  import { PageData, UnparsedSourceFile, UserDefinedOpenApiConfig } from '../../core/shared/types';
11
10
  import { OpenApiDocsProcessor } from '../../core/openapi/open-api-docs-processor';
11
+ import { writeFiles } from '../file-writer';
12
+ import { pipe } from 'fp-ts/function';
13
+ import * as TE from 'fp-ts/TaskEither';
12
14
 
13
15
  export default function openApi(fileBodies: UnparsedSourceFile[], config: UserDefinedOpenApiConfig) {
14
16
  const manifest = createManifest(new RawBodyParser(fileBodies), reflectionWithLogger);
@@ -18,11 +20,14 @@ export default function openApi(fileBodies: UnparsedSourceFile[], config: UserDe
18
20
  Transpiler.generate(filteredTypes, processor);
19
21
  const generatedFiles = processor.fileBuilder().files();
20
22
 
21
- FileWriter.write(generatedFiles, config.targetDir, (file: PageData) => {
22
- Logger.logSingle(`${file.filePath} processed.`, false, 'green', false);
23
- });
23
+ pipe(
24
+ writeFiles(generatedFiles, config.targetDir, (file: PageData) => {
25
+ Logger.logSingle(`${file.outputDocPath} processed.`, 'green');
26
+ }),
27
+ TE.mapError((error) => Logger.error(error)),
28
+ );
24
29
 
25
- // Error logging
30
+ // Logs any errors that the types might have in their doc comment's error field
26
31
  ErrorLogger.logErrors(filteredTypes);
27
32
  }
28
33
 
@@ -48,9 +53,8 @@ function filterByScopes(manifest: Manifest) {
48
53
  const filteredLogMessage = `Filtered ${
49
54
  manifest.types.length - filteredTypes.length
50
55
  } file(s), only keeping classes annotated as @RestResource.`;
51
- Logger.clear();
52
56
 
53
- Logger.logSingle(filteredLogMessage, false, 'green', false);
54
- Logger.logSingle(`Creating documentation for ${filteredTypes.length} file(s)`, false, 'green', false);
57
+ Logger.logSingle(filteredLogMessage, 'green');
58
+ Logger.logSingle(`Creating documentation for ${filteredTypes.length} file(s)`, 'green');
55
59
  return filteredTypes;
56
60
  }
@@ -44,9 +44,10 @@ export const markdownOptions: { [key: string]: Options } = {
44
44
  describe: "Whether to include the file's meta.xml information: Whether it is active and and the API version",
45
45
  default: defaults.includeMetadata,
46
46
  },
47
- documentationRootDir: {
47
+ linkingStrategy: {
48
48
  type: 'string',
49
- describe: 'The root directory of the documentation. This is used to generate the correct relative paths.',
50
- default: defaults.documentationRootDir,
49
+ describe: 'The strategy to use when linking to other documentation pages.',
50
+ choices: ['relative', 'no-link', 'none'],
51
+ default: defaults.linkingStrategy,
51
52
  },
52
53
  };
@@ -12,7 +12,7 @@ describe('Generates interface documentation', () => {
12
12
 
13
13
  const result = await generateDocs([apexBundleFromRawString(input)])();
14
14
  expect(result).documentationBundleHasLength(1);
15
- assertEither(result, (data) => expect(data.docs[0].filePath).toContain('MyClass'));
15
+ assertEither(result, (data) => expect(data.docs[0].outputDocPath).toContain('MyClass'));
16
16
  });
17
17
 
18
18
  it('returns the type as class', async () => {
@@ -268,9 +268,7 @@ describe('Generates interface documentation', () => {
268
268
  const result = await generateDocs([apexBundleFromRawString(input1), apexBundleFromRawString(input2)])();
269
269
  expect(result).documentationBundleHasLength(2);
270
270
  assertEither(result, (data) =>
271
- expect(data).firstDocContains(
272
- 'This is a description with a [ClassRef](/miscellaneous/ClassRef.md) reference',
273
- ),
271
+ expect(data).firstDocContains('This is a description with a [ClassRef](ClassRef.md) reference'),
274
272
  );
275
273
  });
276
274
 
@@ -304,7 +302,7 @@ describe('Generates interface documentation', () => {
304
302
  const result = await generateDocs([apexBundleFromRawString(input1), apexBundleFromRawString(input2)])();
305
303
  expect(result).documentationBundleHasLength(2);
306
304
  assertEither(result, (data) => expect(data).firstDocContains('See'));
307
- assertEither(result, (data) => expect(data).firstDocContains('[ClassRef](/miscellaneous/ClassRef.md)'));
305
+ assertEither(result, (data) => expect(data).firstDocContains('[ClassRef](ClassRef.md)'));
308
306
  });
309
307
 
310
308
  it('displays sees without links when the reference is not found', async () => {
@@ -17,7 +17,7 @@ describe('Generates enum documentation', () => {
17
17
 
18
18
  const result = await generateDocs([apexBundleFromRawString(input)])();
19
19
  expect(result).documentationBundleHasLength(1);
20
- assertEither(result, (data) => expect(data.docs[0].filePath).toContain('MyEnum'));
20
+ assertEither(result, (data) => expect(data.docs[0].outputDocPath).toContain('MyEnum'));
21
21
  });
22
22
 
23
23
  it('returns the type as enum', async () => {
@@ -211,7 +211,7 @@ describe('Generates enum documentation', () => {
211
211
  expect(result).documentationBundleHasLength(2);
212
212
  assertEither(result, (data) => expect(data).firstDocContains('Description'));
213
213
  assertEither(result, (data) =>
214
- expect(data).firstDocContains('This is a description with a [EnumRef](/miscellaneous/EnumRef.md) reference'),
214
+ expect(data).firstDocContains('This is a description with a [EnumRef](EnumRef.md) reference'),
215
215
  );
216
216
  });
217
217
 
@@ -245,7 +245,7 @@ describe('Generates enum documentation', () => {
245
245
  const result = await generateDocs([apexBundleFromRawString(input1), apexBundleFromRawString(input2)])();
246
246
  expect(result).documentationBundleHasLength(2);
247
247
  assertEither(result, (data) => expect(data).firstDocContains('See'));
248
- assertEither(result, (data) => expect(data).firstDocContains('[EnumRef](/miscellaneous/EnumRef.md)'));
248
+ assertEither(result, (data) => expect(data).firstDocContains('[EnumRef](EnumRef.md)'));
249
249
  });
250
250
 
251
251
  it('displays sees without links when the reference is not found', async () => {
@@ -189,9 +189,7 @@ describe('Generates interface documentation', () => {
189
189
  const result = await generateDocs([apexBundleFromRawString(input1), apexBundleFromRawString(input2)])();
190
190
  expect(result).documentationBundleHasLength(2);
191
191
  assertEither(result, (data) =>
192
- expect(data).firstDocContains(
193
- 'This is a description with a [InterfaceRef](/miscellaneous/InterfaceRef.md) reference',
194
- ),
192
+ expect(data).firstDocContains('This is a description with a [InterfaceRef](InterfaceRef.md) reference'),
195
193
  );
196
194
  });
197
195
 
@@ -225,7 +223,7 @@ describe('Generates interface documentation', () => {
225
223
  const result = await generateDocs([apexBundleFromRawString(input1), apexBundleFromRawString(input2)])();
226
224
  expect(result).documentationBundleHasLength(2);
227
225
  assertEither(result, (data) => expect(data).firstDocContains('See'));
228
- assertEither(result, (data) => expect(data).firstDocContains('[InterfaceRef](/miscellaneous/InterfaceRef.md)'));
226
+ assertEither(result, (data) => expect(data).firstDocContains('[InterfaceRef](InterfaceRef.md)'));
229
227
  });
230
228
 
231
229
  it('displays sees without links when the reference is not found', async () => {
@@ -453,7 +451,9 @@ describe('Generates interface documentation', () => {
453
451
  const result = await generateDocs([apexBundleFromRawString(input1), apexBundleFromRawString(input2)])();
454
452
  expect(result).documentationBundleHasLength(2);
455
453
  assertEither(result, (data) =>
456
- expect(data.docs.find((doc) => doc.filePath.includes('AnotherInterface'))?.content).toContain('Inherited'),
454
+ expect(data.docs.find((doc) => doc.outputDocPath.includes('AnotherInterface'))?.content).toContain(
455
+ 'Inherited',
456
+ ),
457
457
  );
458
458
  });
459
459
  });
@@ -25,10 +25,10 @@ describe('Generates a Reference Guide', () => {
25
25
  expect(result).documentationBundleHasLength(2);
26
26
 
27
27
  assertEither(result, (data) =>
28
- expect((data.referenceGuide as ReferenceGuidePageData).content).toContain('[MyEnum](/miscellaneous/MyEnum.md)'),
28
+ expect((data.referenceGuide as ReferenceGuidePageData).content).toContain('[MyEnum](miscellaneous/MyEnum.md)'),
29
29
  );
30
30
  assertEither(result, (data) =>
31
- expect((data.referenceGuide as ReferenceGuidePageData).content).toContain('[MyClass](/miscellaneous/MyClass.md)'),
31
+ expect((data.referenceGuide as ReferenceGuidePageData).content).toContain('[MyClass](miscellaneous/MyClass.md)'),
32
32
  );
33
33
  });
34
34
 
@@ -174,7 +174,7 @@ describe('Generates a Reference Guide', () => {
174
174
  const result = await generateDocs([apexBundleFromRawString(input1), apexBundleFromRawString(input2)])();
175
175
  expect(result).documentationBundleHasLength(2);
176
176
  assertEither(result, (data) =>
177
- expect((data.referenceGuide as ReferenceGuidePageData).content).toContain('with a [MyClass](/group2/MyClass.md)'),
177
+ expect((data.referenceGuide as ReferenceGuidePageData).content).toContain('with a [MyClass](group2/MyClass.md)'),
178
178
  );
179
179
  });
180
180
  });
@@ -17,7 +17,7 @@ export function generateDocs(apexBundles: UnparsedSourceFile[], config?: Partial
17
17
  defaultGroupName: 'Miscellaneous',
18
18
  sortMembersAlphabetically: true,
19
19
  referenceGuideTemplate: referenceGuideTemplate,
20
+ linkingStrategy: 'relative',
20
21
  ...config,
21
- documentationRootDir: '',
22
22
  });
23
23
  }
@@ -15,7 +15,7 @@ const defaultMarkdownGeneratorConfig: MarkdownGeneratorConfig = {
15
15
  defaultGroupName: 'Miscellaneous',
16
16
  referenceGuideTemplate: '',
17
17
  sortMembersAlphabetically: false,
18
- documentationRootDir: '',
18
+ linkingStrategy: 'relative',
19
19
  };
20
20
 
21
21
  describe('Conversion from InterfaceMirror to InterfaceSource understandable by the templating engine', () => {
@@ -0,0 +1,130 @@
1
+ import { generateLink } from '../generate-link';
2
+
3
+ describe('Generates links', () => {
4
+ describe('relative', () => {
5
+ it('generates relative links from the base when found', () => {
6
+ const references = {
7
+ referenceName: {
8
+ referencePath: 'referencePath',
9
+ displayName: 'displayName',
10
+ },
11
+ };
12
+ const from = '__base__';
13
+ const referenceName = 'referenceName';
14
+
15
+ const result = generateLink('relative')(references, from, referenceName);
16
+
17
+ expect(result).toEqual({
18
+ __type: 'link',
19
+ title: 'displayName',
20
+ url: 'referencePath',
21
+ });
22
+ });
23
+
24
+ it('returns the name of the reference when not found', () => {
25
+ const references = {};
26
+ const from = '__base__';
27
+ const referenceName = 'referenceName';
28
+
29
+ const result = generateLink('relative')(references, from, referenceName);
30
+
31
+ expect(result).toEqual('referenceName');
32
+ });
33
+
34
+ it('returns a relative path when linking from a file', () => {
35
+ const references = {
36
+ referenceName: {
37
+ referencePath: 'a/referencePath',
38
+ displayName: 'displayName',
39
+ },
40
+ from: {
41
+ referencePath: 'b/fromPath',
42
+ displayName: 'fromName',
43
+ },
44
+ };
45
+ const from = 'from';
46
+ const referenceName = 'referenceName';
47
+
48
+ const result = generateLink('relative')(references, from, referenceName);
49
+
50
+ expect(result).toEqual({
51
+ __type: 'link',
52
+ title: 'displayName',
53
+ url: '../a/referencePath',
54
+ });
55
+ });
56
+
57
+ it('returns the display name when the from reference is not found', () => {
58
+ const references = {
59
+ referenceName: {
60
+ referencePath: 'a/referencePath',
61
+ displayName: 'displayName',
62
+ },
63
+ };
64
+ const from = 'from';
65
+ const referenceName = 'referenceName';
66
+
67
+ const result = generateLink('relative')(references, from, referenceName);
68
+
69
+ expect(result).toEqual('displayName');
70
+ });
71
+ });
72
+
73
+ describe('no-link', () => {
74
+ it('returns the name of the reference when the reference is not found', () => {
75
+ const references = {};
76
+ const from = '__base__';
77
+ const referenceName = 'referenceName';
78
+
79
+ const result = generateLink('no-link')(references, from, referenceName);
80
+
81
+ expect(result).toEqual('referenceName');
82
+ });
83
+
84
+ it('returns the display name of the reference when the reference is found', () => {
85
+ const references = {
86
+ referenceName: {
87
+ referencePath: 'referencePath',
88
+ displayName: 'displayName',
89
+ },
90
+ };
91
+ const from = '__base__';
92
+ const referenceName = 'referenceName';
93
+
94
+ const result = generateLink('no-link')(references, from, referenceName);
95
+
96
+ expect(result).toEqual('displayName');
97
+ });
98
+ });
99
+
100
+ describe('none', () => {
101
+ it('returns the path as is when the reference is found', () => {
102
+ const references = {
103
+ referenceName: {
104
+ referencePath: 'referencePath',
105
+ displayName: 'displayName',
106
+ },
107
+ };
108
+ const from = '__base__';
109
+ const referenceName = 'referenceName';
110
+
111
+ const result = generateLink('none')(references, from, referenceName);
112
+
113
+ expect(result).toEqual({
114
+ __type: 'link',
115
+ title: 'displayName',
116
+ url: 'referencePath',
117
+ });
118
+ });
119
+
120
+ it('returns the name of the reference when the reference is not found', () => {
121
+ const references = {};
122
+ const from = '__base__';
123
+ const referenceName = 'referenceName';
124
+
125
+ const result = generateLink('none')(references, from, referenceName);
126
+
127
+ expect(result).toEqual('referenceName');
128
+ });
129
+ });
130
+ });
@@ -98,7 +98,6 @@ export function adaptDocumentable(
98
98
  .map((currentAnnotation) => currentAnnotation.body) ?? []
99
99
  );
100
100
  }
101
-
102
101
  return {
103
102
  ...adaptDescribable(documentable.docComment?.descriptionLines, linkGenerator),
104
103
  annotations: documentable.annotations.map((annotation) => annotation.type.toUpperCase()),