Skip to content

Instantly share code, notes, and snippets.

@rodrigocfd
Last active December 23, 2024 18:13
Show Gist options
  • Select an option

  • Save rodrigocfd/e259e16b408f0bbe7c570c3f0a67d69a to your computer and use it in GitHub Desktop.

Select an option

Save rodrigocfd/e259e16b408f0bbe7c570c3f0a67d69a to your computer and use it in GitHub Desktop.
Extending TypeScript built-in types
/**
* Extensões de objetos nativos
* As implementações estão em src/util/func/extends.ts
*/
export {};
declare global {
interface Array<T> {
/** Checks if indexOf() !== -1. */
contains(elem: T): boolean;
/** Checks if findIndex() !== -1. */
containsIf(callback: (elem: T, index: number) => boolean): boolean;
/** Returns a Map with the elements grouped by the given field. */
groupIntoMap<K extends keyof T>(fieldName: K): Map<T[K], T[]>;
/** Returns true if the array contains no elements. */
isEmpty(): boolean;
/** Returns true if the array contains at least 1 element. */
isNotEmpty(): boolean;
/** Returns the last element in the array, if any. */
last(): T | undefined;
/** Removes the elements to which indexOf() !== -1. */
remove(elem: T): void;
/** Removes the elements to which the callback returns true. */
removeIf(callback: (elem: T, index: number) => boolean): void;
/** Replaces the elements to which the callback returns true. */
replaceIf(callback: (elemt: T, index: number) => boolean, newVal: T): void;
/** Returns a new array with no repeated elements. */
unique(): T[];
}
interface Array<T extends Record<K, PropertyKey>> {
/** Returns an object where the key is the specified field, with all items grouped under it. */
groupIntoObject<K extends keyof {[P in keyof T as T[P] extends PropertyKey ? P : never]: any;}>(fieldName: K): Record<T[K], T[]>; // eslint-disable-line @typescript-eslint/no-explicit-any
}
interface Date {
/** Adds (or subtracts, if negative) the given number of days to the date. */
addDays(numDays: number): void;
/** Retorna: Seg 01/01/2023 17:00. */
fmtDataHoraCurta(): string;
/** Returns the number of days in the month to which the date belongs. */
getDaysInTheMonth(): number;
/** Returns the first day of next (or previous, if negative) month. */
getNextMonth(numMonths: number) : Date;
/** Returns the first day of next (or previous, if negative) year. */
getNextYear(numYears: number) : Date;
/** Retorna o nome da semana em português. */
getNomeDiaDaSemana(): string;
/** Retorna o nome da semana em português, abreviado para 3 letras. */
getNomeDiaDaSemanaAbrev(): string;
/** Retorna o nome do dia da semana em português. */
getNomeMes(): string;
}
interface HTMLCollectionOf<T> {
/** Calls the function on each element. */
forEach(callback: (elem: T, index: number) => void): void;
/** Returns true if the collection contains no elements. */
isEmpty(): boolean;
/** Returns true if the collection contains at least 1 element. */
isNotEmpty(): boolean;
}
interface Map<K, V> {
/** Returns true if the map contains no elements. */
isEmpty(): boolean;
/** Returns true if the map contains at least 1 element. */
isNotEmpty(): boolean;
/** Returns the number of keys. */
keyCount(): number;
/** Returns a new array populated with the results of calling a function with each key/value. */
mapIntoArray<U>(callback: (key: K, value: V, index: number) => U): U[];
/** Returns a new Map by mapping the keys. */
mapKeys<U>(callback: (key: K, value: V) => U): Map<U, V>;
/** Returns a new Map by mapping the values under each key. */
mapValues<U>(callback: (key: K, value: V) => U): Map<K, U>;
/** Returns an accumulated value created with the results of calling a function with each key/value. */
reduce<U>(callback: (accumulator: U, key: K, value: V, index: number) => U, initialValue: U): U;
/** Sorts the keys of the Map, in-place; returns a reference to the same Map. */
sortKeys(compareFn: (a: K, b: K) => number): Map<K, V>;
}
interface Number {
/** Formata o número com padrão brasileiro: ponto separador de milhar e vírgula. */
fmtBr(casasDecimais = 0): string;
/** Converts the number to string adding zeros at left, so the number has, at least, the given length. */
lpad(minLength: number): string;
/** Correctly rounds the number to N decimal places, returning a float; zeros are kept. */
round(decimalPlaces: number): number;
}
interface ObjectConstructor {
/** Returns true if the object has no keys with values. */
isEmpty(o: object): boolean;
/** Returns true if the object contains at least 1 key with a value. */
isNotEmpty(o: object): boolean;
/** Returns a new array populated with the results of calling a function with each key/value. */
mapIntoArray<K extends PropertyKey, V, U>(o: Record<K, V>, callback: (key: K, value: V, index: number) => U): U[];
/** Returns a new object with every value mapped under the same key. */
mapValues<K extends PropertyKey, V, U>(o: Record<K, V>, callback: (key: K, value: V, index: number) => U): Record<K, U>;
/** Returns an accumulated value created with the results of calling a function with each key/value. */
reduce<K extends PropertyKey, V, U>(o: Record<K, V>, callback: (accumulator: U, key: K, value: V, index: number) => U, initialValue: U): U;
/** Removes the elements to which the callback returns true. */
removeIf<K extends PropertyKey, V>(o: Record<K, V>, callback: (key: K, value: V, index: number) => boolean): void;
}
interface String {
/** Returns a new string with the first character in uppercase. */
capitalize(): string;
/** Checks if the string contains the substring. */
contains(substr: string): boolean;
/** Returns true if the string containts no characters, or just spaces. */
isBlank(): boolean;
/** Returns true if the string contains no characters. */
isEmpty(): boolean;
/** Returns true if the strings corresponds to an integer, either positive or negative. */
isInteger(): boolean;
/** Returns true if the string contains at least 1 non-space character. */
isNotBlank(): boolean;
/** Returns true if the string containst at least 1 character. */
isNotEmpty(): boolean;
/** Returns true if the string corresponds to a positive integer. */
isPositiveInteger(): boolean;
/** Returns the last character in the string, if any. */
last(): string | undefined;
}
interface URLSearchParams {
/** Returns a new array by iterating each key and value. */
map<U>(callback: (key: string, value: string, index: number) => U): U[];
}
}
/**
* Extensões de objetos nativos
* As declarações TypeScript estão em src/extends.d.ts
*/
export {};
Object.defineProperty(Array.prototype, 'contains', {
enumerable: false,
writable: false,
configurable: false,
value: function contains<T>(elem: T): boolean {
return this.indexOf(elem) !== -1;
},
});
Object.defineProperty(Array.prototype, 'containsIf', {
enumerable: false,
writable: false,
configurable: false,
value: function containsIf<T>(callback: (elem: T, index: number) => boolean): boolean {
return this.findIndex(callback) !== -1;
},
});
Object.defineProperty(Array.prototype, 'groupIntoMap', {
enumerable: false,
writable: false,
configurable: false,
value: function groupIntoMap<T, K extends keyof T>(fieldName: K): Map<T[K], T[]> {
const groups = new Map<T[K], T[]>();
for (const elem of this) {
if (!groups.has(elem[fieldName])) {
groups.set(elem[fieldName], [elem]);
} else {
groups.get(elem[fieldName])!.push(elem);
}
}
return groups;
},
});
Object.defineProperty(Array.prototype, 'groupIntoObject', {
enumerable: false,
writable: false,
configurable: false,
value: function groupIntoObject< // https://stackoverflow.com/a/71068491
T extends Record<K, PropertyKey>,
K extends keyof {[P in keyof T as T[P] extends PropertyKey ? P : never]: any;}, // eslint-disable-line @typescript-eslint/no-explicit-any
>(fieldName: K): Record<T[K], T[]> {
return this.reduce((accum: Record<T[K], T[]>, item: T) => {
if (accum[item[fieldName]] === undefined) {
accum[item[fieldName]] = [item];
} else {
accum[item[fieldName]].push(item);
}
return accum;
}, {} as Record<T[K], T[]>);
},
});
Object.defineProperty(Array.prototype, 'isEmpty', {
enumerable: false,
writable: false,
configurable: false,
value: function isEmpty(): boolean {
return this.length === 0;
},
});
Object.defineProperty(Array.prototype, 'isNotEmpty', {
enumerable: false,
writable: false,
configurable: false,
value: function isNotEmpty(): boolean {
return this.length > 0;
},
});
Object.defineProperty(Array.prototype, 'last', {
enumerable: false,
writable: false,
configurable: false,
value: function last<T>(): T | undefined {
return this.length === 0 ? undefined : this[this.length - 1];
},
});
Object.defineProperty(Array.prototype, 'remove', {
enumerable: false,
writable: false,
configurable: false,
value: function remove<T>(elem: T): void {
for (;;) {
const idx = this.indexOf(elem);
if (idx === -1) break;
this.splice(idx, 1);
}
},
});
Object.defineProperty(Array.prototype, 'removeIf', {
enumerable: false,
writable: false,
configurable: false,
value: function removeIf<T>(callback: (elem: T, index: number) => boolean): void {
let idx = 0;
while (idx < this.length) {
if (callback(this[idx], idx)) {
this.splice(idx, 1);
} else {
++idx;
}
}
},
});
Object.defineProperty(Array.prototype, 'replaceIf', {
enumerable: false,
writable: false,
configurable: false,
value: function replaceIf<T>(
callback: (elem: T, index: number) => boolean,
newVal: T,
): void {
for (let i = 0; i < this.length; ++i) {
if (callback(this[i], i)) {
this[i] = newVal;
}
}
},
});
Object.defineProperty(Array.prototype, 'unique', {
enumerable: false,
writable: false,
configurable: false,
value: function unique<T>(): T[] {
const uniques = [] as T[];
for (const elem of this) {
if (!uniques.contains(elem))
uniques.push(elem);
}
return uniques;
},
});
//------------------------------------------------------------------------------
Object.defineProperty(Date.prototype, 'addDays', {
enumerable: false,
writable: false,
configurable: false,
value: function addDays(numDays: number): void {
this.setUTCDate(this.getUTCDate() + numDays);
},
});
Object.defineProperty(Date.prototype, 'fmtDataHoraCurta', {
enumerable: false,
writable: false,
configurable: false,
value: function fmtDataHoraCurta(): string {
return this.getNomeDiaDaSemanaAbrev().capitalize()
+ ' ' + this.getDate().toString().padStart(2, '0')
+ '/' + (this.getMonth() + 1).toString().padStart(2, '0')
+ '/' + this.getFullYear()
+ ' ' + this.getHours().toString().padStart(2, '0')
+ ':' + this.getMinutes().toString().padStart(2, '0');
},
});
Object.defineProperty(Date.prototype, 'getDaysInTheMonth', {
enumerable: false,
writable: false,
configurable: false,
value: function getDaysInTheMonth(): number {
return new Date(this.getFullYear(), this.getMonth() + 1, 0).getDate();
},
});
Object.defineProperty(Date.prototype, 'getNextMonth', {
enumerable: false,
writable: false,
configurable: false,
value: function getNextMonth(numMonths: number): Date {
return new Date(this.getFullYear(), this.getMonth() + numMonths, 1);
},
});
Object.defineProperty(Date.prototype, 'getNextYear', {
enumerable: false,
writable: false,
configurable: false,
value: function getNextMonth(numYears: number): Date {
return new Date(this.getFullYear() + numYears, this.getMonth(), 1);
},
});
Object.defineProperty(Date.prototype, 'getNomeDiaDaSemana', {
enumerable: false,
writable: false,
configurable: false,
value: function getNomeDiaDaSemana(): string {
return ['domingo', 'segunda', 'terça', 'quarta', 'quinta', 'sexta', 'sábado'][this.getDay()];
},
});
Object.defineProperty(Date.prototype, 'getNomeDiaDaSemanaAbrev', {
enumerable: false,
writable: false,
configurable: false,
value: function getNomeDiaDaSemanaAbrev(): string {
return ['dom', 'seg', 'ter', 'qua', 'qui', 'sex', 'sáb'][this.getDay()];
},
});
Object.defineProperty(Date.prototype, 'getNomeMes', {
enumerable: false,
writable: false,
configurable: false,
value: function getNomeMes(): string {
return ['janeiro', 'fevereiro', 'março', 'abril', 'maio', 'junho',
'julho', 'agosto', 'setembro', 'outubro', 'novembro', 'dezembro'][this.getMonth()];
},
});
//------------------------------------------------------------------------------
Object.defineProperty(HTMLCollection.prototype, 'forEach', {
enumerable: false,
writable: false,
configurable: false,
value: function forEach<T>(callback: (elem: T, index: number) => void): void {
for (let i = 0; i < this.length; ++i)
callback(this[i], i);
},
});
Object.defineProperty(HTMLCollection.prototype, 'isEmpty', {
enumerable: false,
writable: false,
configurable: false,
value: function isEmpty(): boolean {
return this.length === 0;
},
});
Object.defineProperty(HTMLCollection.prototype, 'isNotEmpty', {
enumerable: false,
writable: false,
configurable: false,
value: function isNotEmpty(): boolean {
return this.length > 0;
},
});
//------------------------------------------------------------------------------
Object.defineProperty(Map.prototype, 'isEmpty', {
enumerable: false,
writable: false,
configurable: false,
value: function isEmpty(): boolean {
return this.size === 0;
},
});
Object.defineProperty(Map.prototype, 'isNotEmpty', {
enumerable: false,
writable: false,
configurable: false,
value: function isNotEmpty(): boolean {
return this.size > 0;
},
});
Object.defineProperty(Map.prototype, 'keyCount', {
enumerable: false,
writable: false,
configurable: false,
value: function keyCount(): number {
return Array.from(this.keys()).length;
},
});
Object.defineProperty(Map.prototype, 'mapIntoArray', {
enumerable: false,
writable: false,
configurable: false,
value: function mapIntoArray<K, V, U>(callback: (key: K, value: V, index: number) => U): U[] {
const result = [] as U[];
let count = 0;
for (const [key, val] of this) {
result.push(callback(key, val, count++));
}
return result;
},
});
Object.defineProperty(Map.prototype, 'mapKeys', {
enumerable: false,
writable: false,
configurable: false,
value: function mapKeys<K, V, U>(callback: (key: K, value: V) => U): Map<U, V> {
const result = new Map<U, V>();
for (const [key, _val] of this) {
const val = this.get(key);
result.set(callback(key, val), val);
}
return result;
},
});
Object.defineProperty(Map.prototype, 'mapValues', {
enumerable: false,
writable: false,
configurable: false,
value: function mapValues<K, V, U>(callback: (key: K, value: V) => U): Map<K, U> {
const result = new Map<K, U>();
for (const [key, val] of this) {
result.set(key, callback(key, val));
}
return result;
},
});
Object.defineProperty(Map.prototype, 'reduce', {
enumerable: false,
writable: false,
configurable: false,
value: function reduce<K, V, U>(
callback: (accumulator: U, key: K, value: V, index: number) => U,
initialValue: U,
): U {
let accum = initialValue;
let count = 0;
for (const [key, val] of this) {
accum = callback(accum, key, val, count++);
}
return accum;
},
});
Object.defineProperty(Map.prototype, 'sortKeys', {
enumerable: false,
writable: false,
configurable: false,
value: function sortKeys<K, V>(compareFn: (a: K, b: K) => number): Map<K, V> {
const keys = Array.from(this.keys()) as K[];
const tmpMap = new Map<K, V>();
for (const sortedKey of keys.sort(compareFn)) {
tmpMap.set(sortedKey, this.get(sortedKey));
}
this.clear();
for (const [key, val] of tmpMap.entries()) {
this.set(key, val);
}
return this;
},
});
//------------------------------------------------------------------------------
Object.defineProperty(Number.prototype, 'fmtBr', {
enumerable: false,
writable: false,
configurable: false,
value: function fmtBr(casasDecimais = 0): string {
return this.toLocaleString('pt-BR', {
minimumFractionDigits: casasDecimais,
maximumFractionDigits: casasDecimais,
});
},
});
Object.defineProperty(Number.prototype, 'lpad', {
enumerable: false,
writable: false,
configurable: false,
value: function lpad(minLength: number): string {
return this.toString().padStart(minLength, '0');
},
});
Object.defineProperty(Number.prototype, 'round', {
enumerable: false,
writable: false,
configurable: false,
value: function round(decimalPlaces: number): number {
return Math.round((this + Number.EPSILON) * Math.pow(10, decimalPlaces))
/ Math.pow(10, decimalPlaces);
},
});
//------------------------------------------------------------------------------
Object.isEmpty = function isEmpty(o: object): boolean { // https://stackoverflow.com/a/28020863/6923555
return Object.keys(o).length === 0;
};
Object.isNotEmpty = function isNotEmpty(o: object): boolean {
return Object.keys(o).length > 0;
};
Object.mapIntoArray = function mapIntoArray<K extends PropertyKey, V, U>(
o: Record<K, V>,
callback: (key: K, value: V, index: number) => U,
): U[] {
const arr = [];
let count = 0;
for (const key in o) {
arr.push(callback(key, o[key], count++));
}
return arr;
};
Object.mapValues = function mapValues<K extends PropertyKey, V, U>(
o: Record<K, V>,
callback: (key: K, value: V, index: number) => U,
): Record<K, U> {
const accum = {} as Record<K, U>;
let count = 0;
for (const key in o) {
accum[key] = callback(key, o[key], count++);
}
return accum;
};
Object.reduce = function reduce<K extends PropertyKey, V, U>(
o: Record<K, V>,
callback: (accumulator: U, key: K, value: V, index: number) => U,
initialValue: U,
): U {
let accum = initialValue;
let count = 0;
for (const key in o) {
accum = callback(accum, key, o[key], count++);
}
return accum;
};
Object.removeIf = function<K extends PropertyKey, V>(
o: Record<K, V>,
callback: (key: K, value: V, index: number) => boolean,
): void {
let count = 0;
for (const key in o) {
if (callback(key, o[key], count++))
delete o[key];
}
};
//------------------------------------------------------------------------------
Object.defineProperty(String.prototype, 'capitalize', {
enumerable: false,
writable: false,
configurable: false,
value: function capitalize(): string {
return this.length === 0 ? ''
: this.charAt(0).toUpperCase() + this.slice(1).toLowerCase();
},
});
Object.defineProperty(String.prototype, 'contains', {
enumerable: false,
writable: false,
configurable: false,
value: function contains(substr: string): boolean {
return this.indexOf(substr) !== -1;
},
});
Object.defineProperty(String.prototype, 'isBlank', {
enumerable: false,
writable: false,
configurable: false,
value: function isBlank(): boolean {
return this.trim().length === 0;
},
});
Object.defineProperty(String.prototype, 'isEmpty', {
enumerable: false,
writable: false,
configurable: false,
value: function isEmpty(): boolean {
return this.length === 0;
},
});
Object.defineProperty(String.prototype, 'isInteger', {
enumerable: false,
writable: false,
configurable: false,
value: function isInteger(): boolean {
if (this.length === 0) return false;
for (let i = 0; i < this.length; ++i) {
const code = this.charCodeAt(i);
const isNum = code >= 48 && code <= 57; // 0 to 9
if (i === 0) {
if (this[0] !== '-' && !isNum) return false; // first char can be a minus sign
} else {
if (!isNum) return false;
}
}
return true;
},
});
Object.defineProperty(String.prototype, 'isNotBlank', {
enumerable: false,
writable: false,
configurable: false,
value: function isNotBlank(): boolean {
return this.trim().length > 0;
},
});
Object.defineProperty(String.prototype, 'isNotEmpty', {
enumerable: false,
writable: false,
configurable: false,
value: function isNotEmpty(): boolean {
return this.length > 0;
},
});
Object.defineProperty(String.prototype, 'isPositiveInteger', {
enumerable: false,
writable: false,
configurable: false,
value: function isPositiveInteger(): boolean {
if (this.length === 0) return false;
for (let i = 0; i < this.length; ++i) {
const code = this.charCodeAt(i);
const isNum = code >= 48 && code <= 57; // 0 to 9
if (!isNum) return false;
}
return true;
},
});
Object.defineProperty(String.prototype, 'last', {
enumerable: false,
writable: false,
configurable: false,
value: function last(): string | undefined {
return this.length === 0 ? undefined : this[this.length - 1];
},
});
//------------------------------------------------------------------------------
Object.defineProperty(URLSearchParams.prototype, 'map', {
enumerable: false,
writable: false,
configurable: false,
value: function map<U>(callback: (key: string, value: string, index: number) => U): U[] {
const entries = Array.from(this.entries()) as [string, string][];
const result = [] as U[];
let count = 0;
for (const [key, val] of entries) {
result.push(callback(key, val, count++));
}
return result;
},
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment