Skip to content

Commit

Permalink
Merge PR #174 from nblockchain/wip/fp
Browse files Browse the repository at this point in the history
Start using FP patterns in TypeScript.

TODO: move FP helpers to npmjs package?
  • Loading branch information
knocte committed Sep 10, 2024
2 parents 131a410 + 6807c3d commit aed7a80
Show file tree
Hide file tree
Showing 6 changed files with 344 additions and 88 deletions.
112 changes: 53 additions & 59 deletions commitlint.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,19 @@ let bodyMaxLineLength = 64;
let headerMaxLineLength = 50;
let footerMaxLineLength = 150;

function notNullStringErrorMessage(stringType: string): string {
return `This is unexpected because ${stringType} should never be null`;
function notStringErrorMessage(variableName: string): string {
return `This is unexpected because ${variableName} should have been a string`;
}

function extractStringFromCommitlintParam(
paramName: string,
variable: any
): string {
let str = Helpers.assertNotNone(
Helpers.convertAnyToString(variable),
notStringErrorMessage(paramName)
);
return str;
}

export default {
Expand Down Expand Up @@ -64,27 +75,21 @@ export default {
{
rules: {
"body-prose": ({ raw }: { raw: any }) => {
let rawStr = Helpers.assertNotNull(
Helpers.convertAnyToString(raw, "raw"),
notNullStringErrorMessage("raw")
);
let rawStr = extractStringFromCommitlintParam("raw", raw);

return Plugins.bodyProse(rawStr);
},

"commit-hash-alone": ({ raw }: { raw: any }) => {
let rawStr = Helpers.assertNotNull(
Helpers.convertAnyToString(raw, "raw"),
notNullStringErrorMessage("raw")
);
let rawStr = extractStringFromCommitlintParam("raw", raw);

return Plugins.commitHashAlone(rawStr);
},

"empty-wip": ({ header }: { header: any }) => {
let headerStr = Helpers.assertNotNull(
Helpers.convertAnyToString(header, "header"),
notNullStringErrorMessage("header")
let headerStr = extractStringFromCommitlintParam(
"header",
header
);

return Plugins.emptyWip(headerStr);
Expand All @@ -95,9 +100,9 @@ export default {
_: any,
maxLineLength: number
) => {
let headerStr = Helpers.assertNotNull(
Helpers.convertAnyToString(header, "header"),
notNullStringErrorMessage("header")
let headerStr = extractStringFromCommitlintParam(
"header",
header
);

return Plugins.headerMaxLengthWithSuggestions(
Expand All @@ -107,15 +112,12 @@ export default {
},

"footer-notes-misplacement": ({ body }: { body: any }) => {
let bodyStr = Helpers.convertAnyToString(body, "body");
return Plugins.footerNotesMisplacement(bodyStr);
let maybeBody = Helpers.convertAnyToString(body);
return Plugins.footerNotesMisplacement(maybeBody);
},

"footer-refs-validity": ({ raw }: { raw: any }) => {
let rawStr = Helpers.assertNotNull(
Helpers.convertAnyToString(raw, "raw"),
notNullStringErrorMessage("raw")
);
let rawStr = extractStringFromCommitlintParam("raw", raw);

return Plugins.footerRefsValidity(rawStr);
},
Expand All @@ -125,72 +127,67 @@ export default {
}: {
header: any;
}) => {
let headerStr = Helpers.assertNotNull(
Helpers.convertAnyToString(header, "header"),
notNullStringErrorMessage("header")
let headerStr = extractStringFromCommitlintParam(
"header",
header
);

return Plugins.preferSlashOverBackslash(headerStr);
},

"proper-issue-refs": ({ raw }: { raw: any }) => {
let rawStr = Helpers.assertNotNull(
Helpers.convertAnyToString(raw, "raw"),
notNullStringErrorMessage("raw")
);
let rawStr = extractStringFromCommitlintParam("raw", raw);

return Plugins.properIssueRefs(rawStr);
},

"title-uppercase": ({ header }: { header: any }) => {
let headerStr = Helpers.assertNotNull(
Helpers.convertAnyToString(header, "header"),
notNullStringErrorMessage("header")
let headerStr = extractStringFromCommitlintParam(
"header",
header
);

return Plugins.titleUppercase(headerStr);
},

"too-many-spaces": ({ raw }: { raw: any }) => {
let rawStr = Helpers.assertNotNull(
Helpers.convertAnyToString(raw, "raw"),
notNullStringErrorMessage("raw")
);
let rawStr = extractStringFromCommitlintParam("raw", raw);

return Plugins.tooManySpaces(rawStr);
},

"type-space-after-colon": ({ header }: { header: any }) => {
let headerStr = Helpers.assertNotNull(
Helpers.convertAnyToString(header, "header"),
notNullStringErrorMessage("header")
let headerStr = extractStringFromCommitlintParam(
"header",
header
);

return Plugins.typeSpaceAfterColon(headerStr);
},

"type-with-square-brackets": ({ header }: { header: any }) => {
let headerStr = Helpers.assertNotNull(
Helpers.convertAnyToString(header, "header"),
notNullStringErrorMessage("header")
let headerStr = extractStringFromCommitlintParam(
"header",
header
);

return Plugins.typeWithSquareBrackets(headerStr);
},

// NOTE: we use 'header' instead of 'subject' as a workaround to this bug: https://github.com/conventional-changelog/commitlint/issues/3404
"subject-lowercase": ({ header }: { header: any }) => {
let headerStr = Helpers.assertNotNull(
Helpers.convertAnyToString(header, "header"),
notNullStringErrorMessage("header")
let headerStr = extractStringFromCommitlintParam(
"header",
header
);

return Plugins.subjectLowercase(headerStr);
},

"type-space-after-comma": ({ header }: { header: any }) => {
let headerStr = Helpers.assertNotNull(
Helpers.convertAnyToString(header, "header"),
notNullStringErrorMessage("header")
let headerStr = Helpers.assertNotNone(
Helpers.convertAnyToString(header),
notStringErrorMessage("header")
);

return Plugins.typeSpaceAfterComma(headerStr);
Expand All @@ -201,35 +198,32 @@ export default {
_: any,
maxLineLength: number
) => {
let bodyStr = Helpers.convertAnyToString(body, "body");
let maybeBody = Helpers.convertAnyToString(body);
return Plugins.bodySoftMaxLineLength(
bodyStr,
maybeBody,
maxLineLength
);
},

"body-paragraph-line-min-length": ({ body }: { body: any }) => {
let bodyStr = Helpers.convertAnyToString(body, "body");
let maybeBody = Helpers.convertAnyToString(body);
return Plugins.bodyParagraphLineMinLength(
bodyStr,
maybeBody,
headerMaxLineLength,
bodyMaxLineLength
);
},

"trailing-whitespace": ({ raw }: { raw: any }) => {
let rawStr = Helpers.assertNotNull(
Helpers.convertAnyToString(raw, "raw"),
notNullStringErrorMessage("raw")
);
let rawStr = extractStringFromCommitlintParam("raw", raw);

return Plugins.trailingWhitespace(rawStr);
},

"type-space-before-paren": ({ header }: { header: any }) => {
let headerStr = Helpers.assertNotNull(
Helpers.convertAnyToString(header, "header"),
notNullStringErrorMessage("header")
let headerStr = extractStringFromCommitlintParam(
"header",
header
);

return Plugins.typeSpaceBeforeParen(headerStr);
Expand Down
79 changes: 79 additions & 0 deletions commitlint/fpHelpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
interface IOption {
/**
* @deprecated it is better to use `if (foo instanceof None)` so that you can access the .value in the `else` case
**/
IsNone(): boolean;
/**
* @deprecated it is better to use `if (!(foo instanceof None))` so that you can access the .value inside the `if` block
**/
IsSome(): boolean;
}

export class None {
public IsNone(): boolean {
return true;
}
public IsSome(): boolean {
return false;
}

/**
* @deprecated it is better to use `OptionStatic.None`
**/
constructor() {}
}
export class Some<T> {
value: T;

constructor(val: NonNullable<T>) {
this.value = val;
}

public IsNone(): boolean {
return false;
}
public IsSome(): boolean {
return true;
}
}

export type Option<T> = (None | Some<NonNullable<T>>) & IOption;

export class OptionStatic {
public static None = new None();
public static OfObj<T>(obj: T | null | undefined): Option<NonNullable<T>> {
if (obj === null || obj === undefined) {
return OptionStatic.None;
} else {
return new Some(obj);
}
}
}

export class TypeHelpers {
public static IsNullOrUndefined(variable: any) {
return variable === null || variable === undefined;
}

// because instanceof doesn't work with primitive types (e.g. String), taken from https://stackoverflow.com/a/58184883/544947
public static IsInstanceOf(variable: any, type: any) {
if (TypeHelpers.IsNullOrUndefined(variable)) {
throw new Error(
"Invalid 'variable' parameter passed in: null or undefined"
);
}
if (TypeHelpers.IsNullOrUndefined(type)) {
throw new Error(
"Invalid 'type' parameter passed in: null or undefined"
);
}

let res: boolean = false;
if (typeof type == "string") {
res = typeof variable == type.toLowerCase();
} else {
res = variable.constructor == type;
}
return res;
}
}
29 changes: 22 additions & 7 deletions commitlint/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { None, Some, Option, OptionStatic, TypeHelpers } from "./fpHelpers.js";

export abstract class Helpers {
public static errMessageSuffix =
"\nFor reference, these are the guidelines that include our commit message conventions: https://github.com/nblockchain/conventions/blob/master/docs/WorkflowGuidelines.md";
Expand All @@ -16,12 +18,25 @@ export abstract class Helpers {
}

// to convert from 'any' type
public static convertAnyToString(
potentialString: any,
paramName: string
): string | null {
// this null/undefined check is required, otherwise, String(null) might give us the stupid string "null"
return potentialString ? String(potentialString) : null;
public static convertAnyToString(potentialString: any): Option<string> {
if (TypeHelpers.IsNullOrUndefined(potentialString)) {
return OptionStatic.None;
}
// this type check is required, otherwise, String(null) would give us the stupid string "null"
if (TypeHelpers.IsInstanceOf(potentialString, String)) {
return new Some(String(potentialString));
}
return OptionStatic.None;
}

public static assertNotNone(
text: Option<string>,
errorMessage: string
): string {
if (text instanceof None) {
throw new Error(errorMessage);
}
return text.value;
}

public static assertNotNull(
Expand Down Expand Up @@ -75,7 +90,7 @@ export abstract class Helpers {

public static findUrls(text: string) {
var urlRegex = /(https?:\/\/[^\s]+)/g;
return text.match(urlRegex);
return OptionStatic.OfObj(text.match(urlRegex));
}

public static isCommitUrl(url: string) {
Expand Down
Loading

0 comments on commit aed7a80

Please sign in to comment.