Skip to content

Commit

Permalink
Add sourceLinkTemplate option
Browse files Browse the repository at this point in the history
  • Loading branch information
Gerrit0 committed Oct 18, 2022
1 parent 2eaa476 commit 4c81b3d
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 66 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Unreleased

### Features

- Added `titleLink`, `navigationLinks` and `sidebarLinks` options to add additional links to the rendered output, #1830.
- Added `sourceLinkTemplate` option to allow more flexible specification of remote urls.
Deprecated now redundant `gitRevision` detection starting with `https?://` introduced in v0.23.16, #2068.

### Thanks!

- @futurGH

## v0.23.16 (2022-10-10)

### Features
Expand Down
11 changes: 5 additions & 6 deletions src/lib/converter/plugins/SourcePlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ export class SourcePlugin extends ConverterComponent {
@BindOption("gitRemote")
readonly gitRemote!: string;

@BindOption("sourceLinkTemplate")
readonly sourceLinkTemplate!: string;

@BindOption("basePath")
readonly basePath!: string;

Expand Down Expand Up @@ -141,12 +144,7 @@ export class SourcePlugin extends ConverterComponent {
for (const source of refl.sources || []) {
if (gitIsInstalled) {
const repo = this.getRepository(source.fullFileName);
source.url = repo?.getURL(source.fullFileName);
if (source.url) {
source.url += `#${repo!.getLineNumberAnchor(
source.line
)}`;
}
source.url = repo?.getURL(source.fullFileName, source.line);
}

source.fileName = normalizePath(
Expand Down Expand Up @@ -182,6 +180,7 @@ export class SourcePlugin extends ConverterComponent {
// Try to create a new repository
const repository = Repository.tryCreateRepository(
dirName,
this.sourceLinkTemplate,
this.gitRevision,
this.gitRemote,
this.application.logger
Expand Down
69 changes: 32 additions & 37 deletions src/lib/converter/utils/repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,25 +28,18 @@ export class Repository {
*/
files = new Set<string>();

/**
* The base url for link creation.
*/
baseUrl: string;

/**
* The anchor prefix used to select lines, usually `L`
*/
anchorPrefix: string;
urlTemplate: string;
gitRevision: string;

/**
* Create a new Repository instance.
*
* @param path The root path of the repository.
*/
constructor(path: string, baseUrl: string) {
constructor(path: string, gitRevision: string, urlTemplate: string) {
this.path = path;
this.baseUrl = baseUrl;
this.anchorPrefix = guessAnchorPrefix(this.baseUrl);
this.gitRevision = gitRevision;
this.urlTemplate = urlTemplate;

const out = git("-C", path, "ls-files");
if (out.status === 0) {
Expand All @@ -64,16 +57,21 @@ export class Repository {
* @param fileName The file whose URL should be determined.
* @returns A URL pointing to the web preview of the given file or undefined.
*/
getURL(fileName: string): string | undefined {
getURL(fileName: string, line: number): string | undefined {
if (!this.files.has(fileName)) {
return;
}

return `${this.baseUrl}/${fileName.substring(this.path.length + 1)}`;
}
const replacements = {
gitRevision: this.gitRevision,
path: fileName.substring(this.path.length + 1),
line,
};

getLineNumberAnchor(lineNumber: number): string {
return `${this.anchorPrefix}${lineNumber}`;
return this.urlTemplate.replace(
/\{(gitRevision|path|line)\}/g,
(_, key) => replacements[key as never]
);
}

/**
Expand All @@ -87,6 +85,7 @@ export class Repository {
*/
static tryCreateRepository(
path: string,
sourceLinkTemplate: string,
gitRevision: string,
gitRemote: string,
logger: Logger
Expand All @@ -103,14 +102,18 @@ export class Repository {
).stdout.trim();
if (!gitRevision) return; // Will only happen in a repo with no commits.

let baseUrl: string | undefined;
if (/^https?:\/\//.test(gitRemote)) {
baseUrl = `${gitRemote}/${gitRevision}`;
let urlTemplate: string | undefined;
if (sourceLinkTemplate) {
urlTemplate = sourceLinkTemplate;
} else if (/^https?:\/\//.test(gitRemote)) {
logger.warn(
"Using a link as the gitRemote is deprecated and will be removed in 0.24."
);
urlTemplate = `${gitRemote}/{gitRevision}`;
} else {
const remotesOut = git("-C", path, "remote", "get-url", gitRemote);
if (remotesOut.status === 0) {
baseUrl = guessBaseUrl(
gitRevision,
urlTemplate = guessSourceUrlTemplate(
remotesOut.stdout.split("\n")
);
} else {
Expand All @@ -120,11 +123,12 @@ export class Repository {
}
}

if (!baseUrl) return;
if (!urlTemplate) return;

return new Repository(
BasePath.normalize(topLevel.stdout.replace("\n", "")),
baseUrl
gitRevision,
urlTemplate
);
}
}
Expand All @@ -142,10 +146,7 @@ const repoExpressions = [
/(gitlab.com)[:/]([^/]+)\/(.*)/,
];

export function guessBaseUrl(
gitRevision: string,
remotes: string[]
): string | undefined {
export function guessSourceUrlTemplate(remotes: string[]): string | undefined {
let hostname = "";
let user = "";
let project = "";
Expand All @@ -168,19 +169,13 @@ export function guessBaseUrl(
}

let sourcePath = "blob";
let anchorPrefix = "L";
if (hostname.includes("gitlab")) {
sourcePath = "-/blob";
} else if (hostname.includes("bitbucket")) {
sourcePath = "src";
anchorPrefix = "lines-";
}

return `https://${hostname}/${user}/${project}/${sourcePath}/${gitRevision}`;
}

function guessAnchorPrefix(url: string) {
if (url.includes("bitbucket")) {
return "lines-";
}

return "L";
return `https://${hostname}/${user}/${project}/${sourcePath}/{gitRevision}/{path}#${anchorPrefix}{line}`;
}
1 change: 1 addition & 0 deletions src/lib/utils/options/declaration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ export interface TypeDocOptionMap {
excludeTags: `@${string}`[];
readme: string;
cname: string;
sourceLinkTemplate: string;
gitRevision: string;
gitRemote: string;
htmlLang: string;
Expand Down
4 changes: 4 additions & 0 deletions src/lib/utils/options/sources/typedoc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,10 @@ export function addTypeDocOptions(options: Pick<Options, "addDeclaration">) {
name: "cname",
help: "Set the CNAME file text, it's useful for custom domains on GitHub Pages.",
});
options.addDeclaration({
name: "sourceLinkTemplate",
help: "Specify a link template to be used when generating source urls. If not set, will be automatically created using the git remote. Supports {path}, {line}, {gitRevision} placeholders.",
});
options.addDeclaration({
name: "gitRevision",
help: "Use specified revision instead of the last revision for linking to GitHub/Bitbucket source files.",
Expand Down
46 changes: 23 additions & 23 deletions src/test/Repository.test.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
import { guessBaseUrl } from "../lib/converter/utils/repository";
import { guessSourceUrlTemplate } from "../lib/converter/utils/repository";
import { strictEqual as equal } from "assert";

describe("Repository", function () {
describe("guessBaseUrl helper", () => {
describe("guessSourceUrlTemplate helper", () => {
it("handles a personal GitHub HTTPS URL", () => {
const mockRemotes = ["https://github.com/joebloggs/foobar.git"];

equal(
guessBaseUrl("rev", mockRemotes),
"https://github.com/joebloggs/foobar/blob/rev"
guessSourceUrlTemplate(mockRemotes),
"https://github.com/joebloggs/foobar/blob/{gitRevision}/{path}#L{line}"
);
});

it("handles a personal GitHub SSH URL", () => {
const mockRemotes = ["git@github.com:TypeStrong/typedoc.git"];

equal(
guessBaseUrl("rev", mockRemotes),
"https://github.com/TypeStrong/typedoc/blob/rev"
guessSourceUrlTemplate(mockRemotes),
"https://github.com/TypeStrong/typedoc/blob/{gitRevision}/{path}#L{line}"
);
});

it("handles an enterprise GitHub URL", () => {
const mockRemotes = ["git@github.acme.com:joebloggs/foobar.git"];
equal(
guessBaseUrl("rev", mockRemotes),
"https://github.acme.com/joebloggs/foobar/blob/rev"
guessSourceUrlTemplate(mockRemotes),
"https://github.acme.com/joebloggs/foobar/blob/{gitRevision}/{path}#L{line}"
);
});

Expand All @@ -34,8 +34,8 @@ describe("Repository", function () {
"ssh://org@bigcompany.githubprivate.com/joebloggs/foobar.git",
];
equal(
guessBaseUrl("rev", mockRemotes),
"https://bigcompany.githubprivate.com/joebloggs/foobar/blob/rev"
guessSourceUrlTemplate(mockRemotes),
"https://bigcompany.githubprivate.com/joebloggs/foobar/blob/{gitRevision}/{path}#L{line}"
);
});

Expand All @@ -44,8 +44,8 @@ describe("Repository", function () {
"ssh://org@bigcompany.ghe.com/joebloggs/foobar.git",
];
equal(
guessBaseUrl("rev", mockRemotes),
"https://bigcompany.ghe.com/joebloggs/foobar/blob/rev"
guessSourceUrlTemplate(mockRemotes),
"https://bigcompany.ghe.com/joebloggs/foobar/blob/{gitRevision}/{path}#L{line}"
);
});

Expand All @@ -55,8 +55,8 @@ describe("Repository", function () {
];

equal(
guessBaseUrl("rev", mockRemotes),
"https://bigcompany.github.us/joebloggs/foobar/blob/rev"
guessSourceUrlTemplate(mockRemotes),
"https://bigcompany.github.us/joebloggs/foobar/blob/{gitRevision}/{path}#L{line}"
);
});

Expand All @@ -65,38 +65,38 @@ describe("Repository", function () {
"https://joebloggs@bitbucket.org/joebloggs/foobar.git",
];
equal(
guessBaseUrl("rev", mockRemotes),
"https://bitbucket.org/joebloggs/foobar/src/rev"
guessSourceUrlTemplate(mockRemotes),
"https://bitbucket.org/joebloggs/foobar/src/{gitRevision}/{path}#lines-{line}"
);
});

it("handles a bitbucket SSH URL", () => {
const mockRemotes = ["git@bitbucket.org:joebloggs/foobar.git"];
equal(
guessBaseUrl("rev", mockRemotes),
"https://bitbucket.org/joebloggs/foobar/src/rev"
guessSourceUrlTemplate(mockRemotes),
"https://bitbucket.org/joebloggs/foobar/src/{gitRevision}/{path}#lines-{line}"
);
});

it("handles a GitLab URL", () => {
const mockRemotes = ["https://gitlab.com/joebloggs/foobar.git"];
equal(
guessBaseUrl("rev", mockRemotes),
"https://gitlab.com/joebloggs/foobar/-/blob/rev"
guessSourceUrlTemplate(mockRemotes),
"https://gitlab.com/joebloggs/foobar/-/blob/{gitRevision}/{path}#L{line}"
);
});

it("handles a GitLab SSH URL", () => {
const mockRemotes = ["git@gitlab.com:joebloggs/foobar.git"];
equal(
guessBaseUrl("rev", mockRemotes),
"https://gitlab.com/joebloggs/foobar/-/blob/rev"
guessSourceUrlTemplate(mockRemotes),
"https://gitlab.com/joebloggs/foobar/-/blob/{gitRevision}/{path}#L{line}"
);
});

it("Gracefully handles unknown urls", () => {
const mockRemotes = ["git@example.com"];
equal(guessBaseUrl("rev", mockRemotes), undefined);
equal(guessSourceUrlTemplate(mockRemotes), undefined);
});
});
});

0 comments on commit 4c81b3d

Please sign in to comment.