Skip to content

Commit

Permalink
feat: report typescript diagnostics (optional)
Browse files Browse the repository at this point in the history
  • Loading branch information
skarab42 committed Jul 8, 2022
1 parent 90ed862 commit 276fb9e
Show file tree
Hide file tree
Showing 13 changed files with 177 additions and 23 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"@types/node": "^18.0.0",
"@typescript-eslint/eslint-plugin": "^5.29.0",
"@typescript-eslint/parser": "^5.29.0",
"byots": "^4.8.0-dev.20220707.1.35",
"eslint": "^8.18.0",
"eslint-config-prettier": "^8.5.0",
"lint-staged": "^13.0.3",
Expand Down
10 changes: 10 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 32 additions & 0 deletions src/plugin/error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
export function createErrorString({
message,
file,
line,
column,
}: {
message: string;
file: string;
line: number;
column: number;
}) {
return `
// error generated by vite-plugin-vitest-typescript-assert
(() => {
const err = new TypeError("${message}");
err.name = "TypeError";
err.stack = "";
err.stackStr = "";
err.stacks = [{
file: "${file}",
line: ${line + 1},
column: ${column},
sourcePos: {
source: "${file}",
line: ${line + 1},
column: ${column}
}
}];
throw err;
})()
`;
}
28 changes: 28 additions & 0 deletions src/plugin/identifiers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// TODO: should be set by user config?!
export const testWrapperIdentifiers = [
'it',
'it.skip',
'it.skipIf',
'it.runIf',
'it.only',
'it.concurrent',
'it.todo',
'it.fails',
'it.each',
'test',
'test.skip',
'test.skipIf',
'test.runIf',
'test.only',
'test.concurrent',
'test.todo',
'test.fails',
'test.each',
'describe',
'describe.skip',
'describe.only',
'describe.concurrent',
'describe.shuffle',
'describe.todo',
'describe.each',
];
7 changes: 6 additions & 1 deletion src/plugin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,12 @@ export function vitestTypescriptAssertPlugin(options: PluginOptions = {}): Plugi
return;
}

return transform({ code, fileName, tsconfig: tsconfig.config });
return transform({
code,
fileName,
tsconfig: tsconfig.config,
shouldReportDiagnostics: !!typescript?.shouldReportDiagnostics,
});
},
};
}
13 changes: 9 additions & 4 deletions src/plugin/transform.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
import type ts from 'typescript';
import type ts from 'byots';
import MagicString from 'magic-string';
import { reportDiagnostics } from './util';
import type { TransformResult } from 'vite';
import { program } from '../typescript/program';

export interface TransformOptions {
code: string;
fileName: string;
tsconfig: ts.ParsedCommandLine;
shouldReportDiagnostics?: boolean;
}

export function transform({ code, fileName, tsconfig }: TransformOptions): TransformResult {
export function transform({ code, fileName, tsconfig, shouldReportDiagnostics }: TransformOptions): TransformResult {
const { diagnostics } = program({ config: tsconfig, fileName });
const newCode = new MagicString(code);

// eslint-disable-next-line no-console
console.log({ fileName, tsconfig });
if (shouldReportDiagnostics) {
reportDiagnostics(diagnostics, newCode, fileName);
}

return {
code: newCode.toString(),
Expand Down
53 changes: 53 additions & 0 deletions src/plugin/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import ts from 'byots';
import type MagicString from 'magic-string';
import { createErrorString } from './error';
import { newLine } from '../typescript/util';
import { testWrapperIdentifiers } from './identifiers';

export function searchTestWrapperFromPosition(file: ts.SourceFile, position: number) {
const token = ts.getTokenAtPosition(file, position);

let parent: ts.Node | undefined = token.parent;

while (parent) {
if (ts.isCallExpression(parent)) {
const expression = parent.expression;
const identifier = expression.getText();

if (testWrapperIdentifiers.includes(identifier)) {
const name = parent.arguments[0]?.getText().slice(1, -1);
const handler = parent.arguments[1];

if (name && handler) {
return { name, expression, handler };
}
}

parent = undefined;
}

parent = parent?.parent;
}

return undefined;
}

export function reportDiagnostics(diagnostics: readonly ts.Diagnostic[], newCode: MagicString, fileName: string) {
if (diagnostics[0]) {
const { messageText, start, file } = diagnostics[0];

if (file) {
const startPosition = start ?? -1;
const message = ts.flattenDiagnosticMessageText(messageText, newLine);
const { line, character } = ts.getLineAndCharacterOfPosition(file, startPosition);
const token = searchTestWrapperFromPosition(file, startPosition);

if (token) {
const position = token.handler.getEnd() - 1;
newCode.appendLeft(position, createErrorString({ message, file: fileName, line, column: character }));
} else {
newCode.append(createErrorString({ message, file: fileName, line, column: character }));
}
}
}
}
13 changes: 2 additions & 11 deletions src/typescript/config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import path from 'path';
import ts from 'typescript';
import ts from 'byots';
import { formatDiagnostics } from './diagnostic';
import { errorMessage, ErrorCode } from '../error';
import { getCurrentDirectory, fileExists, readFile } from './util';
Expand All @@ -8,6 +8,7 @@ export interface TypeScriptConfigOptions {
configName?: string;
searchPath?: string;
compilerOptions?: ts.CompilerOptions;
shouldReportDiagnostics?: boolean;
}

export function findConfigFile(options: TypeScriptConfigOptions = {}) {
Expand Down Expand Up @@ -88,13 +89,3 @@ export function loadConfig(options?: TypeScriptConfigOptions) {
return { error: error as Error };
}
}

export function getCompilerOptions(options?: TypeScriptConfigOptions, compilerOptions?: ts.CompilerOptions) {
return {
...ts.getDefaultCompilerOptions(),
strict: true,
target: ts.ScriptTarget.Latest,
...(options?.compilerOptions ?? {}),
...(compilerOptions ?? {}),
} as ts.CompilerOptions;
}
2 changes: 1 addition & 1 deletion src/typescript/diagnostic.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import ts from 'typescript';
import ts from 'byots';
import { getCurrentDirectory, newLine } from './util';

export const diagnosticsHost: ts.FormatDiagnosticsHost = {
Expand Down
10 changes: 10 additions & 0 deletions src/typescript/program.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import ts from 'byots';

export function program({ fileName, config }: { fileName: string; config: ts.ParsedCommandLine }) {
const compilerOptions = { ...ts.getDefaultCompilerOptions(), ...config.options };
const program = ts.createProgram([fileName], compilerOptions);
const diagnostics = ts.getPreEmitDiagnostics(program);
const checker = program.getTypeChecker();

return { program, diagnostics, checker };
}
9 changes: 7 additions & 2 deletions src/typescript/util.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import ts from 'typescript';
import ts from 'byots';

export const newLine = '\r\n';
export const newLine = '\n';

export function getCurrentDirectory() {
return ts.sys.getCurrentDirectory();
Expand All @@ -13,3 +13,8 @@ export function fileExists(fileName: string) {
export function readFile(fileName: string) {
return ts.sys.readFile(fileName);
}

export function getMiddle(node: ts.Node) {
const diff = node.getEnd() - node.getStart();
return node.getStart() + Math.round(diff / 2);
}
16 changes: 13 additions & 3 deletions test/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
import { test, expect, describe } from 'vitest';

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const prout1 = 'plop';

test('test-1', () => {
expect('Hello World').toBe(42);

// const prout2 = 'plop';
});

describe('test-2', () => {
test('test-2', () => {
expect('Hello World').toBe(24);
describe('describe-1', () => {
// const prout3 = 'plop';

test('test-3', () => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const prout4 = 'plop';

// expect('Hello World').toBe(24);
});
});
6 changes: 5 additions & 1 deletion vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,9 @@ import { defineConfig } from 'vitest/config';
import { vitestTypescriptAssertPlugin } from './src';

export default defineConfig({
plugins: [vitestTypescriptAssertPlugin()],
plugins: [
vitestTypescriptAssertPlugin({
typescript: { shouldReportDiagnostics: true },
}),
],
});

0 comments on commit 276fb9e

Please sign in to comment.