Skip to content

Commit

Permalink
Merge pull request #689 from Mokshit06/tsc-diagnostic
Browse files Browse the repository at this point in the history
Add type-checking to unplugin
  • Loading branch information
edemaine authored Sep 14, 2023
2 parents 939c059 + 9713a7c commit cfa1cc7
Show file tree
Hide file tree
Showing 10 changed files with 762 additions and 777 deletions.
22 changes: 20 additions & 2 deletions build/esbuild.civet
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,8 @@ for esm in [false, true]
outfile: "dist/main.#{if esm then 'mjs' else 'js'}"
plugins: [
resolveExtensions
civetPlugin()
heraPlugin
civetPlugin()
]
}).catch -> process.exit 1

Expand All @@ -106,8 +106,8 @@ esbuild.build({
outfile: 'dist/browser.js'
plugins: [
resolveExtensions
civetPlugin()
heraPlugin
civetPlugin()
]
}).catch -> process.exit 1

Expand All @@ -125,3 +125,21 @@ esbuild.build({
civetPlugin()
]
}).catch -> process.exit 1


for format in ["esm", "cjs"]
esbuild.build({
entryPoints: ['source/ts-diagnostic.civet']
bundle: false
sourcemap
minify
watch
platform: 'node'
format: format
target: "esNext"
outdir: 'dist'
outExtension: { ".js": if format == "esm" then ".mjs" else ".js" }
plugins: [
civetPlugin({ dts: true })
]
}).catch -> process.exit 1
2 changes: 1 addition & 1 deletion build/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
set -euo pipefail

c8 mocha "$@"
tsc
tsc --noEmit
4 changes: 3 additions & 1 deletion integration/unplugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,14 +75,16 @@ interface PluginOptions {
dts?: boolean;
outputExtension?: string;
js?: boolean;
typecheck?: boolean;
transformOutput?: (
code: string,
id: string
) => TransformResult | Promise<TransformResult>;
}
```

- `dts`: `unplugin-civet` also supports generating `.d.ts` type definition files from the civet source, which is useful for building libraries. Default: `false`.
- `dts`: Whether to generate `.d.ts` type definition files from the Civet source, which is useful for building libraries. Default: `false`
- `typecheck`: Whether to run type checking on the generated code. Default: `false`.
- `outputExtension`: Output filename extension to use. Default: `.civet.tsx`, or uses `.civet.jsx` if `js` is `true`.
- `js`: Whether to transpile to JS or TS. Default: `false`.
- `transformOutput(code, id)`: Adds a custom transformer over jsx/tsx code produced by `civet.compile`. It gets passed the jsx/tsx source (`code`) and filename (`id`), and should return valid jsx/tsx code.
1 change: 1 addition & 0 deletions integration/unplugin/examples/rollup/package.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"type": "module",
"scripts": {
"build": "npx rollup --config rollup.config.js"
},
Expand Down
127 changes: 101 additions & 26 deletions integration/unplugin/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
import { TransformResult, createUnplugin } from 'unplugin';
import civet from '@danielx/civet';
import {
TransformResult,
createUnplugin,
SourceMapCompact as UnpluginSourceMap,
} from 'unplugin';
import civet, { SourceMap } from '@danielx/civet';
import {
remapRange,
flattenDiagnosticMessageText,
// @ts-ignore
// using ts-ignore because the version of @danielx/civet typescript is checking against
// is the one published to npm, not the one in the repo
} from '@danielx/civet/ts-diagnostic';
import * as fs from 'fs';
import path from 'path';
import ts from 'typescript';
Expand All @@ -22,10 +33,12 @@ export type PluginOptions = {
} & ( // Eliminates the possibility of having both `dts` and `js` set to `true`
| {
dts?: false;
typecheck?: false;
js?: false | true;
}
| {
dts?: true;
typecheck?: true;
js?: false;
}
);
Expand All @@ -38,17 +51,22 @@ const civetUnplugin = createUnplugin((options: PluginOptions = {}) => {
throw new Error("Can't have both `dts` and `js` be set to `true`.");
}

const transpileToJS = options.js ?? !options.dts;
if (options.typecheck && options.js) {
throw new Error("Can't have both `typecheck` and `js` be set to `true`.");
}

const transpileToJS = options.js ?? !(options.dts || options.typecheck);
const outExt = options.outputExtension ?? (transpileToJS ? '.jsx' : '.tsx');

let fsMap: Map<string, string> = new Map();
const sourceMaps = new Map<string, SourceMap>();
let compilerOptions: any;

return {
name: 'unplugin-civet',
enforce: 'pre',
async buildStart() {
if (options.dts) {
if (options.dts || options.typecheck) {
const configPath = ts.findConfigFile(process.cwd(), ts.sys.fileExists);

if (!configPath) {
Expand Down Expand Up @@ -79,7 +97,7 @@ const civetUnplugin = createUnplugin((options: PluginOptions = {}) => {
}
},
buildEnd() {
if (options.dts) {
if (options.dts || options.typecheck) {
const system = tsvfs.createFSBackedSystem(fsMap, process.cwd(), ts);
const host = tsvfs.createVirtualCompilerHost(
system,
Expand All @@ -92,21 +110,63 @@ const civetUnplugin = createUnplugin((options: PluginOptions = {}) => {
host: host.compilerHost,
});

for (const file of fsMap.keys()) {
const sourceFile = program.getSourceFile(file)!;
program.emit(
sourceFile,
(filePath, content) => {
this.emitFile({
source: content,
fileName: path.relative(process.cwd(), filePath),
type: 'asset',
});
},
undefined,
true
const diagnostics: ts.Diagnostic[] = ts
.getPreEmitDiagnostics(program)
.map(diagnostic => {
const file = diagnostic.file;
if (!file) return diagnostic;

const sourceMap = sourceMaps.get(file.fileName);
if (!sourceMap) return diagnostic;

const sourcemapLines = sourceMap.data.lines;
const range = remapRange(
{
start: diagnostic.start || 0,
end: (diagnostic.start || 0) + (diagnostic.length || 1),
},
sourcemapLines
);

return {
...diagnostic,
messageText: flattenDiagnosticMessageText(diagnostic.messageText),
length: diagnostic.length,
start: range.start,
};
});

if (diagnostics.length > 0) {
console.error(
ts.formatDiagnosticsWithColorAndContext(diagnostics, formatHost)
);
}

if (options.dts) {
for (const file of fsMap.keys()) {
const sourceFile = program.getSourceFile(file)!;
program.emit(
sourceFile,
async (filePath, content) => {
const dir = path.dirname(filePath);
await fs.promises.mkdir(dir, { recursive: true });

const pathFromDistDir = path.relative(
compilerOptions.outDir ?? process.cwd(),
filePath
);

this.emitFile({
source: content,
fileName: pathFromDistDir,
type: 'asset',
});
},
undefined,
true
);
}
}
}
},
resolveId(id, importer) {
Expand Down Expand Up @@ -134,13 +194,23 @@ const civetUnplugin = createUnplugin((options: PluginOptions = {}) => {
// but for some reason, webpack seems to be running them in the order
// of `resolveId` -> `loadInclude` -> `transform` -> `load`
// so we have to do transformation here instead
const compiled = civet.compile(code, {
// inlineMap: true,
filename: id,
js: transpileToJS,
sourceMap: true,
});

sourceMaps.set(path.resolve(process.cwd(), id), compiled.sourceMap);

const jsonSourceMap = compiled.sourceMap.json(
path.basename(id.replace(/\.tsx$/, '')),
path.basename(id)
);

let transformed: TransformResult = {
code: civet.compile(code, {
inlineMap: true,
filename: id,
js: transpileToJS,
} as any) as string,
map: null,
code: compiled.code,
map: jsonSourceMap as UnpluginSourceMap,
};

if (options.transformOutput)
Expand All @@ -151,8 +221,13 @@ const civetUnplugin = createUnplugin((options: PluginOptions = {}) => {
transform(code, id) {
if (!/\.civet\.tsx?$/.test(id)) return null;

if (options.dts) {
fsMap.set(path.resolve(process.cwd(), id), code);
if (options.dts || options.typecheck) {
const resolved = path.resolve(process.cwd(), id);
fsMap.set(resolved, code);
// Vite and Rollup normalize filenames to use `/` instad of `\`.
// We give the TypeScript VFS both versions just in case.
const slash = resolved.replace(/\\/g, '/');
if (resolved !== slash) fsMap.set(slash, code);
}

return null;
Expand Down
Loading

0 comments on commit cfa1cc7

Please sign in to comment.