Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add type-checking to unplugin #689

Merged
merged 16 commits into from
Sep 14, 2023
Merged
Show file tree
Hide file tree
Changes from 3 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: 6 additions & 6 deletions build/esbuild.civet
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
esbuild = require "esbuild"
heraPlugin = require "@danielx/hera/esbuild-plugin"
# Need to use the packaged version because we may not have built our own yet
civetPlugin = require "../node_modules/@danielx/civet/dist/esbuild-plugin.js"
civetPlugin = (require "../node_modules/@danielx/civet/dist/esbuild-plugin.js").default

watch = process.argv.includes '--watch'
minify = false
Expand Down Expand Up @@ -87,8 +87,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 @@ -102,22 +102,22 @@ esbuild.build({
outfile: 'dist/browser.js'
plugins: [
resolveExtensions
civetPlugin()
heraPlugin
civetPlugin()
]
}).catch -> process.exit 1

esbuild.build({
entryPoints: ['source/bun-civet.civet']
entryPoints: ['source/bun-civet.civet', 'source/ts-diagnostic.civet']
bundle: false
sourcemap
minify
watch
platform: 'node'
format: 'esm'
target: "esNext"
outfile: 'dist/bun-civet.mjs'
outdir: 'dist'
plugins: [
civetPlugin()
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
90 changes: 81 additions & 9 deletions integration/unplugin/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
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,
rangeFromTextSpan,
// @ts-ignore
STRd6 marked this conversation as resolved.
Show resolved Hide resolved
// 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 Down Expand Up @@ -42,6 +54,7 @@ const civetUnplugin = createUnplugin((options: PluginOptions = {}) => {
const outExt = options.outputExtension ?? (transpileToJS ? '.jsx' : '.tsx');

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

return {
Expand Down Expand Up @@ -92,11 +105,51 @@ const civetUnplugin = createUnplugin((options: PluginOptions = {}) => {
host: host.compilerHost,
});

const diagnostics: ts.Diagnostic[] = program
.getGlobalDiagnostics()
.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(
rangeFromTextSpan(
{
start: diagnostic.start || 0,
length: diagnostic.length ?? 1,
},
document
),
sourcemapLines
);

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

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

for (const file of fsMap.keys()) {
const sourceFile = program.getSourceFile(file)!;
program.emit(
sourceFile,
(filePath, content) => {
async (filePath, content) => {
const dir = path.dirname(filePath);
if (!pathExists(dir)) {
await fs.promises.mkdir(dir, { recursive: true });
}
Mokshit06 marked this conversation as resolved.
Show resolved Hide resolved

this.emitFile({
source: content,
fileName: path.relative(process.cwd(), filePath),
Expand Down Expand Up @@ -134,13 +187,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 Down Expand Up @@ -175,4 +238,13 @@ const civetUnplugin = createUnplugin((options: PluginOptions = {}) => {
};
});

async function pathExists(path: string) {
try {
await fs.promises.access(path);
return true;
} catch {
return false;
}
}

export default civetUnplugin;
163 changes: 22 additions & 141 deletions lsp/source/lib/util.mts
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
import ts from "typescript";
import type {
Diagnostic,
DiagnosticMessageChain,
NavigationBarItem,
NavigationTree,
TextSpan,
} from "typescript"
const {
DiagnosticCategory,
ScriptElementKind,
ScriptElementKindModifier,
} = ts
import vs, {
import ts from 'typescript';
import type { NavigationBarItem, NavigationTree } from 'typescript';
edemaine marked this conversation as resolved.
Show resolved Hide resolved
const { ScriptElementKind, ScriptElementKindModifier } = ts;
import {
CompletionItemKind,
DiagnosticSeverity,
DocumentSymbol,
Position,
Range,
SymbolKind,
SymbolTag,
} from "vscode-languageserver";

import { SourceMap } from "@danielx/civet"
import { TextDocument } from "vscode-languageserver-textdocument";
import assert from "assert";

export type SourcemapLines = SourceMap["data"]["lines"]
} from 'vscode-languageserver';

import { SourceMap } from '@danielx/civet';
import { TextDocument } from 'vscode-languageserver-textdocument';
import assert from 'assert';
import {
SourcemapLines,
remapRange,
rangeFromTextSpan,
} from '@danielx/civet/ts-diagnostic';

export {
remapPosition,
SourcemapLines,
convertDiagnostic,
flattenDiagnosticMessageText,
diagnosticCategoryToSeverity,
} from '@danielx/civet/ts-diagnostic';

// https://github.com/microsoft/vscode/blob/main/extensions/typescript-language-features/src/languageFeatures/documentSymbol.ts#L63

Expand Down Expand Up @@ -196,13 +196,6 @@ function parseKindModifier(kindModifiers: string): Set<string> {
return new Set(kindModifiers.split(/,|\s+/g));
}

function rangeFromTextSpan(span: TextSpan, document: TextDocument): Range {
return {
start: document.positionAt(span.start),
end: document.positionAt(span.start + span.length),
}
}

export function makeRange(l1: number, c1: number, l2: number, c2: number) {
return {
start: {
Expand Down Expand Up @@ -288,55 +281,6 @@ export function containsRange(range: Range, otherRange: Range): boolean {
return true;
}

/**
* Take a position in generated code and map it into a position in source code.
* Reverse mapping.
*
* Return position as-is if no sourcemap is available.
*/
export function remapPosition(position: Position, sourcemapLines?: SourcemapLines): Position {
if (!sourcemapLines) return position

const { line, character } = position

const textLine = sourcemapLines[line]
// Return original position if no mapping at this line
if (!textLine?.length) return position

let i = 0, p = 0, l = textLine.length,
lastMapping, lastMappingPosition = 0

while (i < l) {
const mapping = textLine[i]!
p += mapping[0]!

if (mapping.length === 4) {
lastMapping = mapping
lastMappingPosition = p
}

if (p >= character) {
break
}

i++
}

if (lastMapping) {
const srcLine = lastMapping[2]
const srcChar = lastMapping[3]
const newChar = srcChar + character - lastMappingPosition

return {
line: srcLine,
character: newChar,
}
} else {
// console.error("no mapping for ", position)
return position
}
}

export function forwardMap(sourcemapLines: SourcemapLines, position: Position) {
assert("line" in position, "position must have line")
assert("character" in position, "position must have character")
Expand Down Expand Up @@ -385,69 +329,6 @@ export function forwardMap(sourcemapLines: SourcemapLines, position: Position) {
return position
}

/**
* Use sourcemap lines to remap the start and end position of a range.
*/
export function remapRange(range: Range, sourcemapLines?: SourcemapLines,): Range {
return {
start: remapPosition(range.start, sourcemapLines),
end: remapPosition(range.end, sourcemapLines)
}
}

export function flattenDiagnosticMessageText(
diag: string | DiagnosticMessageChain | undefined,
indent = 0
): string {
if (typeof diag === 'string') {
return diag;
} else if (diag === undefined) {
return '';
}
let result = '';
if (indent) {
result += "\n";

for (let i = 0; i < indent; i++) {
result += ' ';
}
}
result += diag.messageText;
indent++;
if (diag.next) {
for (const kid of diag.next) {
result += flattenDiagnosticMessageText(kid, indent);
}
}
return result;
}

export function convertDiagnostic(diagnostic: Diagnostic, document: TextDocument, sourcemapLines?: SourcemapLines): vs.Diagnostic {
return {
message: flattenDiagnosticMessageText(diagnostic.messageText),
range: remapRange(rangeFromTextSpan({
start: diagnostic.start || 0,
length: diagnostic.length ?? 1
}, document), sourcemapLines),
severity: diagnosticCategoryToSeverity(diagnostic.category),
code: diagnostic.code,
source: diagnostic.source || "typescript",
}
}

function diagnosticCategoryToSeverity(category: ts.DiagnosticCategory): DiagnosticSeverity {
switch (category) {
case DiagnosticCategory.Warning:
return DiagnosticSeverity.Warning
case DiagnosticCategory.Error:
return DiagnosticSeverity.Error
case DiagnosticCategory.Suggestion:
return DiagnosticSeverity.Hint
case DiagnosticCategory.Message:
return DiagnosticSeverity.Information
}
}

import type { SourceMap as CoffeeSourceMap } from "coffeescript"
export function convertCoffeeScriptSourceMap(sourceMap: CoffeeSourceMap): SourceMap["data"]["lines"] {
const lines: SourceMap["data"]["lines"] = []
Expand Down
8 changes: 7 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"require": "./dist/main.js"
},
"./babel-plugin": "./dist/babel-plugin.mjs",
"./bun-civet": "./dist/bun-civet.mjs",
"./bun-civet": "./dist/bun-civet.js",
"./esm": "./dist/esm.mjs",
"./esbuild-plugin": "./dist/esbuild-plugin.js",
"./register": "./register.js",
Expand All @@ -35,6 +35,10 @@
"require": "./dist/esbuild.js",
"import": "./dist/esbuild.mjs"
},
"./ts-diagnostic": {
"default": "./dist/ts-diagnostic.js",
edemaine marked this conversation as resolved.
Show resolved Hide resolved
"types": "./dist/ts-diagnostic.civet.d.ts"
},
"./*": "./*",
"./dist/*": "./dist/*"
},
Expand Down Expand Up @@ -83,6 +87,8 @@
"typescript": "^4.7.4",
"vite": "^4.4.9",
"vitepress": "^1.0.0-alpha.35",
"vscode-languageserver": "^8.1.0",
"vscode-languageserver-textdocument": "^1.0.8",
"vue": "^3.2.45"
},
"peerDependencies": {
Expand Down
Loading