Skip to content

Commit

Permalink
refactor(mask): Parser perf improvements (#1164)
Browse files Browse the repository at this point in the history
* Removed extra calls to `parseMaskLiterals` around
the codebase.

* Improved `apply` memory allocations by modifying
an array instead of re-creating the masked string
for each character.
  • Loading branch information
rkaraivanov committed Apr 22, 2024
1 parent 00e89d2 commit 075e89b
Showing 1 changed file with 44 additions and 30 deletions.
74 changes: 44 additions & 30 deletions src/components/mask-input/mask-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,18 +43,17 @@ export class MaskParser {
options: MaskOptions = { format: 'CCCCCCCCCC', promptCharacter: '_' }
) {
this.options = options;
this.parseMaskLiterals();
}

protected literals = new Map<number, string>();
protected _escapedMask = '';

public get literalPositions() {
this.getMaskLiterals();
return Array.from(this.literals.keys());
}

public get escapedMask() {
this.getMaskLiterals();
return this._escapedMask;
}

Expand All @@ -64,7 +63,7 @@ export class MaskParser {

public set mask(value: string) {
this.options.format = value || this.options.format;
this.getMaskLiterals();
this.parseMaskLiterals();
}

public get prompt() {
Expand All @@ -77,7 +76,7 @@ export class MaskParser {
: this.options.promptCharacter;
}

protected getMaskLiterals() {
protected parseMaskLiterals() {
this.literals.clear();
this._escapedMask = this.mask;

Expand Down Expand Up @@ -111,18 +110,30 @@ export class MaskParser {

protected getNonLiteralPositions(mask = '') {
const positions = this.literalPositions;
return Array.from(mask)
.map((_, pos) => (!positions.includes(pos) ? pos : -1))
.filter((pos) => pos > -1);
const result = [];
const iter = Array.from(mask).entries();

for (const [pos, _] of iter) {
if (!positions.includes(pos)) {
result.push(pos);
}
}

return result;
}

protected getRequiredNonLiteralPositions(mask: string) {
const positions = this.literalPositions;
return Array.from(mask)
.map((char, pos) =>
REQUIRED.has(char) && !positions.includes(pos) ? pos : -1
)
.filter((pos) => pos > -1);
const result = [];
const iter = Array.from(mask).entries();

for (const [pos, char] of iter) {
if (REQUIRED.has(char) && !positions.includes(pos)) {
result.push(pos);
}
}

return result;
}

public getPreviousNonLiteralPosition(start: number) {
Expand Down Expand Up @@ -176,13 +187,17 @@ export class MaskParser {
}

public parse(masked = '') {
return Array.from(masked).reduce((prev, char, pos) => {
return `${prev}${
!this.literalPositions.includes(pos) && !this.isPromptChar(char)
? char
: ''
}`;
}, '');
const positions = this.literalPositions;
const result = [];
const iter = Array.from(masked).entries();

for (const [pos, char] of iter) {
!positions.includes(pos) && !this.isPromptChar(char)
? result.push(char)
: result.push('');
}

return result.join('');
}

public isValidString(input = '') {
Expand All @@ -191,28 +206,28 @@ export class MaskParser {
if (required.length > this.parse(input).length) {
return false;
}

return required.every((pos) => {
const char = input.charAt(pos);
return (
char !== undefined &&
this.validate(char, this._escapedMask.charAt(pos)) &&
!this.isPromptChar(char)
);
});
}

public apply(input = '') {
const nonLiteralPositions = this.getNonLiteralPositions(this._escapedMask);
let output = new Array(this._escapedMask.length).fill(this.prompt).join('');
const output = new Array(this._escapedMask.length).fill(this.prompt);

this.literals.forEach((char, pos) => {
output = this.replaceCharAt(output, pos, char);
});
for (const [pos, char] of this.literals.entries()) {
output[pos] = char;
}

if (!input) {
return output;
return output.join('');
}

const nonLiteralPositions = this.getNonLiteralPositions(this._escapedMask);
const values = nonLiteralPositions.map((pos, index) => {
const char = input.charAt(index);
return !this.validate(char, this._escapedMask.charAt(pos)) &&
Expand All @@ -225,11 +240,10 @@ export class MaskParser {
values.splice(nonLiteralPositions.length);
}

let pos = 0;
for (const each of values) {
output = this.replaceCharAt(output, nonLiteralPositions[pos++], each);
for (const [position, char] of values.entries()) {
output[nonLiteralPositions[position]] = char;
}

return output;
return output.join('');
}
}

0 comments on commit 075e89b

Please sign in to comment.