Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion __tests__/_errors.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,4 +144,14 @@ index2.ts:1:1 - Unexpected error.
${extendedDiagnostics}
${verboseFooter}`;

export default { PRETTY, NOT_PRETTY };
const MULTILINE_SOURCE = `
index.ts(1,1): error TS1000: Unexpected error.

1 unexpected_error() as UnexpectedError<
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2 unexpected_error
~~~~~~~~~~~~~~~~~~~~
3 >;
~~~`.trimStart();

export default { PRETTY, NOT_PRETTY, MULTILINE_SOURCE };
15 changes: 14 additions & 1 deletion __tests__/src/blueprints/Parser.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@ describe('blueprint > Parser', () => {
let matcher: RegExp;
let matcherKeys: string[] = [];
let inputModifier: jest.Mock;
let resultModifier: jest.Mock;
let parser: Parser;

beforeEach(() => {
input = '12';
matcher = /^(\d)(\d)/g;
matcherKeys = ['first', 'second'];
inputModifier = jest.fn((input: string) => input);
parser = new Parser(matcher, matcherKeys, inputModifier);
resultModifier = jest.fn((result: object) => result);
parser = new Parser(matcher, matcherKeys, inputModifier, resultModifier);
});

describe('instantiation', () => {
Expand Down Expand Up @@ -51,5 +53,16 @@ describe('blueprint > Parser', () => {
expect(inputModifier).toHaveBeenCalledTimes(1);
expect(inputModifier).toHaveBeenLastCalledWith(input);
});

it('should call resultModifier correctly', () => {
expect(resultModifier).toHaveBeenCalledTimes(0);
parser.parse(input);
expect(resultModifier).toHaveBeenCalledTimes(1);
expect(resultModifier).toHaveBeenLastCalledWith({
_match: input,
first: '1',
second: '2'
});
});
});
});
26 changes: 26 additions & 0 deletions __tests__/src/parsers/default-parser.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,32 @@ describe('parsers > defaultParser', () => {
EXPECTED_RESULTS
);
});

it('should return multiline source correctly', () => {
expect(defaultParser.parse(ERROR_MOCKS.MULTILINE_SOURCE)).toEqual([
{
_match: ERROR_MOCKS.MULTILINE_SOURCE,
file: 'index.ts',
errorCode: 'TS1000',
column: '1',
line: '1',
message: 'Unexpected error.',
source: [
'1 unexpected_error() as UnexpectedError<',
' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~',
'2 unexpected_error',
' ~~~~~~~~~~~~~~~~~~~~',
'3 >;',
' ~~~'
].join('\n'),
sourceClean: [
'unexpected_error() as UnexpectedError<',
' unexpected_error',
'>;'
].join('\n')
}
]);
});
});

describe('pretty format disabled', () => {
Expand Down
17 changes: 15 additions & 2 deletions src/blueprints/Parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ export class Parser<MatcherKeys extends readonly string[] = string[]> {
#matcher: RegExp;
#matcherKeys: MatcherKeys;
#inputModifier?: (input: string) => string;
#resultModifier?: (
result: ParseResult<MatcherKeys>
) => ParseResult<MatcherKeys>;

/**
* Creates a new Parser instance.
Expand All @@ -19,14 +22,18 @@ export class Parser<MatcherKeys extends readonly string[] = string[]> {
constructor(
matcher: RegExp,
matcherKeys: MatcherKeys,
inputModifier?: (input: string) => string
inputModifier?: (input: string) => string,
resultModifier?: (
result: ParseResult<MatcherKeys>
) => ParseResult<MatcherKeys>
) {
if (!matcher.global) {
throw new TypeError("argument 'matcher' must be a global regex.");
}
this.#matcher = matcher;
this.#matcherKeys = matcherKeys;
this.#inputModifier = inputModifier;
this.#resultModifier = resultModifier;
}

/**
Expand Down Expand Up @@ -62,7 +69,13 @@ export class Parser<MatcherKeys extends readonly string[] = string[]> {
);

for (const matchError of matches) {
results.push(this.#constructResult(matchError));
let result = this.#constructResult(matchError);

if (this.#resultModifier) {
result = this.#resultModifier({ ...result });
}

results.push(result);
}

return results;
Expand Down
70 changes: 59 additions & 11 deletions src/parsers/default-parser.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,69 @@
import { Parser } from 'src/blueprints/Parser';
import { type ParseResult, Parser } from 'src/blueprints/Parser';

const KEYS = [
'file',
'line',
'column',
'errorCode',
'message',
'source',
'sourceClean'
] as const;

/**
* Default `Parser` instance.
*/
export const defaultParser = new Parser(
/^(?:(?:(.*?)[:(](\d+)[:,](\d+)[)]? ?[:-] ?)|(?:error ?))(?:error ?)?(TS\d+)?(?:(?:: )|(?: - )|(?: ))(.*(?:\r?\n {2,}.*)*)(?:(?:\r?\n){2,}(\d+\s+(.*)\r?\n\s+~+))?$/gm,
[
'file',
'line',
'column',
'errorCode',
'message',
'source',
'sourceClean'
] as const,
/^(?:(?:(.*?)[:(](\d+)[:,](\d+)[)]? ?[:-] ?)|(?:error ?))(?:error ?)?(TS\d+)?(?:(?:: )|(?: - )|(?: ))(.*(?:\r?\n {2,}.*)*)$(?:(?:\r?\n){2,}^((?:\d+\s+\S.*\r?\n^\s+~+$(?:\r?\n){0,1})*)){0,1}$/gm,
KEYS,
(input) => {
// biome-ignore lint/suspicious/noControlCharactersInRegex: needed for removing colored text
return input.replaceAll(/\x1b\[[0-9;]*m/g, '');
},
(result) => {
result._match = result._match.trimEnd();

if (result.source) {
result.source = result.source.trim();

const matches = Array.from(
result.source.trim().matchAll(/^(?:\d+)(\s.*)$/gm)
);

let minIndent: number;

const codeLines = matches.reduce<string[]>((acc, curr) => {
if (curr?.[1]) {
const indentLength = (curr[1].match(/^(\s+).*$/)?.[1] || '').length;
if (minIndent === undefined) {
minIndent = indentLength;
} else {
minIndent = indentLength < minIndent ? indentLength : minIndent;
}
acc.push(curr[1]);
}
return acc;
}, []);

if (codeLines.length > 0) {
result.sourceClean = codeLines.reduce((acc, curr, currIndex) => {
let res = acc;
res += curr.slice(minIndent);

if (currIndex !== codeLines.length - 1) {
res += '\n';
}

return res;
}, '');
}
}

return result;
}
);

/**
* Default `Parser` result type.
*/
export type DefaultParserResult = ParseResult<typeof KEYS>;