Skip to content

Commit

Permalink
feat(cli): improve error message when encountering unsupported syntax
Browse files Browse the repository at this point in the history
  - Add NotFoundExportedKind error class
  - Add link for GitHub issue creation in CLI error message
  • Loading branch information
imjuni committed Oct 3, 2024
1 parent 6df0185 commit 776b4c8
Show file tree
Hide file tree
Showing 9 changed files with 301 additions and 42 deletions.
8 changes: 2 additions & 6 deletions src/cli/commands/buildCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,14 @@ import { building } from '#/modules/commands/building';
import consola from 'consola';
import type yargs from 'yargs';

async function buildCommandCode(argv: yargs.ArgumentsCamelCase<TCommandBuildArgvOptions>) {
const options = await createBuildOptions(argv);
await building(options);
}

export async function buildCommand(argv: yargs.ArgumentsCamelCase<TCommandBuildArgvOptions>) {
ProgressBar.it.enable = true;
Spinner.it.enable = true;
Reasoner.it.enable = true;

try {
await buildCommandCode(argv);
const options = await createBuildOptions(argv);
await building(options);
} catch (err) {
consola.error(err);
} finally {
Expand Down
16 changes: 6 additions & 10 deletions src/cli/commands/initCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,18 @@ import { initializing } from '#/modules/commands/initializing';
import consola from 'consola';
import type yargs from 'yargs';

async function initCommandCode(argv: yargs.ArgumentsCamelCase<ICommandInitOptions>) {
const option: TCommandInitOptions = {
$kind: CE_CTIX_COMMAND.INIT_COMMAND,
forceYes: argv.forceYes,
};

await initializing(option);
}

export async function initCommand(argv: yargs.ArgumentsCamelCase<ICommandInitOptions>) {
ProgressBar.it.enable = true;
Spinner.it.enable = true;
Reasoner.it.enable = true;

try {
await initCommandCode(argv);
const option: TCommandInitOptions = {
$kind: CE_CTIX_COMMAND.INIT_COMMAND,
forceYes: argv.forceYes,
};

await initializing(option);
} catch (err) {
consola.error(err);
} finally {
Expand Down
14 changes: 4 additions & 10 deletions src/cli/commands/removeCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,6 @@ import { removing } from '#/modules/commands/removing';
import consola from 'consola';
import type yargs from 'yargs';

async function removeCommandCode(
argv: yargs.ArgumentsCamelCase<TCommandRemoveOptions & TCommandBuildArgvOptions>,
) {
const options = await createBuildOptions(argv);
const removeOptions = createRemoveOptions(argv);

await removing({ ...options, ...removeOptions });
}

export async function removeCommand(
argv: yargs.ArgumentsCamelCase<TCommandRemoveOptions & TCommandBuildArgvOptions>,
) {
Expand All @@ -26,7 +17,10 @@ export async function removeCommand(
Reasoner.it.enable = true;

try {
await removeCommandCode(argv);
const options = await createBuildOptions(argv);
const removeOptions = createRemoveOptions(argv);

await removing({ ...options, ...removeOptions });
} catch (err) {
consola.error(err);
} finally {
Expand Down
17 changes: 16 additions & 1 deletion src/cli/ux/Reasoner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ export class Reasoner {
}
}

get logger() {
return this.#logger;
}

get enable() {
return this.#enable;
}
Expand Down Expand Up @@ -88,7 +92,7 @@ export class Reasoner {
} else {
messageBlock.push(
` ${chevronRight} ${chalk.gray(
`${filePath}:${reason.lineAndCharacter.line}:${reason.lineAndCharacter.character}`,
`${filePath}:${chalk.yellowBright(reason.lineAndCharacter.line)}:${chalk.yellowBright(reason.lineAndCharacter.character)}`,
)}`,
);
}
Expand Down Expand Up @@ -120,6 +124,17 @@ export class Reasoner {
this.#logger(warns.join(''));
this.#streamFunc(errors.join(''));
}

displayNewIssueMessage() {
const messageIndent = ' > ';
this.#logger(
chalk.green(
`${messageIndent}Please submit a new GitHub issue with a reproducible repository to improve ctix!`,
),
);
this.#logger(chalk.green(`${messageIndent}https://github.com/imjuni/ctix/issues/new`));
this.#logger('\n');
}
}

Reasoner.bootstrap();
12 changes: 11 additions & 1 deletion src/compilers/getExportedKind.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { getFunctionName } from '#/compilers/getFunctionName';
import { NotFoundExportedKind } from '#/errors/NotFoundExportedKind';
import * as tsm from 'ts-morph';
import { match } from 'ts-pattern';

Expand Down Expand Up @@ -207,7 +208,16 @@ export function getExportedKind(node: tsm.ExportedDeclarations): {
* ```
*/
.otherwise(() => {
throw new Error(`Cannot support type: (${node.getKind()}) ${node.getText()}`);
const sourceFile = node.getSourceFile();
const filePath = sourceFile.getFilePath();
const pos = sourceFile.getLineAndColumnAtPos(node.getStart(false));

throw new NotFoundExportedKind(
pos,
filePath,
node,
`Cannot support type: (${node.getKind()}) ${node.getText()}`,
);
})
);
}
53 changes: 53 additions & 0 deletions src/errors/NotFoundExportedKind.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import type { IReason } from '#/compilers/interfaces/IReason';
import type * as tsm from 'ts-morph';

export class NotFoundExportedKind extends Error {
#pos: { line: number; column: number };

#filePath: string;

#node: tsm.Node;

get pos() {
return this.#pos;
}

get filePath() {
return this.#filePath;
}

get node() {
return this.#node;
}

constructor(
pos: NotFoundExportedKind['pos'],
filePath: string,
node: tsm.Node,
message?: string,
) {
super(message);

this.#pos = pos;
this.#node = node;
this.#filePath = filePath;
}

createReason() {
const message =
`Cannot support export statement: (${this.#node.getKind()}) ${this.#node.getText()}`.trim();

const reason: IReason = {
type: 'error',
lineAndCharacter: {
line: this.#pos.line,
character: this.#pos.column,
},
nodes: [this.#node],
filePath: this.#filePath,
message,
};

return reason;
}
}
124 changes: 124 additions & 0 deletions src/errors/__tests__/not.found.exported.kind.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { NotFoundExportedKind } from '#/errors/NotFoundExportedKind';
import { randomUUID } from 'crypto';
import { atOrThrow } from 'my-easy-fp';
import pathe from 'pathe';
import * as tsm from 'ts-morph';
import { describe, expect, it } from 'vitest';

const tsconfigDirPath = pathe.join(process.cwd(), 'examples');
const tsconfigFilePath = pathe.join(tsconfigDirPath, 'tsconfig.example.json');
const context = {
tsconfig: tsconfigFilePath,
project: new tsm.Project({
tsConfigFilePath: tsconfigFilePath,
}),
};

describe('NotFoundExportedKind', () => {
it('constructor', () => {
const uuid = randomUUID();
const filename = `${uuid}.ts`;
const source = `
/** @ctix-generation-style default-alias-named-destructive */
import path from 'node:path';
/**
* Some Detail Comment
*/
/** @ctix-exclude-next */
const hero = 'ironman';
export default hero;
export function iamfunction() {
return 'function';
}`.trim();

const sourceFile = context.project.createSourceFile(filename, source.trim());
const exportedDeclarationsMap = sourceFile.getExportedDeclarations();
const exportedDeclaration = atOrThrow(exportedDeclarationsMap.get('default') ?? [], 0);

const err = new NotFoundExportedKind(
{ line: 1, column: 1 },
filename,
exportedDeclaration,
'test message',
);

expect(err).toBeTruthy();
});

it('getter and setter', () => {
const uuid = randomUUID();
const filename = `${uuid}.ts`;
const source = `
/** @ctix-generation-style default-alias-named-destructive */
import path from 'node:path';
/**
* Some Detail Comment
*/
/** @ctix-exclude-next */
const hero = 'ironman';
export default hero;
export function iamfunction() {
return 'function';
}`.trim();

const sourceFile = context.project.createSourceFile(filename, source.trim());
const exportedDeclarationsMap = sourceFile.getExportedDeclarations();
const exportedDeclaration = atOrThrow(exportedDeclarationsMap.get('default') ?? [], 0);

const err = new NotFoundExportedKind(
{ line: 1, column: 1 },
filename,
exportedDeclaration,
'test message',
);

expect(err.filePath).toEqual(filename);
expect(err.pos).toEqual({ line: 1, column: 1 });
expect(err.node).toBeTruthy();
});

it('create reason', () => {
const uuid = randomUUID();
const filename = `${uuid}.ts`;
const source = `
/** @ctix-generation-style default-alias-named-destructive */
import path from 'node:path';
/**
* Some Detail Comment
*/
/** @ctix-exclude-next */
const hero = 'ironman';
export default hero;
export function iamfunction() {
return 'function';
}`.trim();

const sourceFile = context.project.createSourceFile(filename, source.trim());
const exportedDeclarationsMap = sourceFile.getExportedDeclarations();
const exportedDeclaration = atOrThrow(exportedDeclarationsMap.get('default') ?? [], 0);

const err = new NotFoundExportedKind(
{ line: 1, column: 1 },
filename,
exportedDeclaration,
'test message',
);

const reason = err.createReason();
expect(reason).toMatchObject({
type: 'error',
lineAndCharacter: { line: 1, character: 1 },
filePath: filename,
message: "Cannot support export statement: (260) hero = 'ironman'",
});
});
});
Loading

0 comments on commit 776b4c8

Please sign in to comment.