diff --git a/CHANGELOG.md b/CHANGELOG.md index 22364ab4d..6aba093d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,12 @@ > - :house: [Internal] > - :nail_care: [Polish] +## master + +#### :rocket: New Feature + +- Paste as JSON.t or ReScript JSX in VSCode. https://github.com/rescript-lang/rescript-vscode/pull/1141 + ## 1.66.0 #### :bug: Bug fix diff --git a/client/src/commands.ts b/client/src/commands.ts index eff3280e0..a94c424a5 100644 --- a/client/src/commands.ts +++ b/client/src/commands.ts @@ -10,6 +10,8 @@ export { openCompiled } from "./commands/open_compiled"; export { switchImplIntf } from "./commands/switch_impl_intf"; export { dumpDebug, dumpDebugRetrigger } from "./commands/dump_debug"; export { dumpServerState } from "./commands/dump_server_state"; +export { pasteAsRescriptJson } from "./commands/paste_as_rescript_json"; +export { pasteAsRescriptJsx } from "./commands/paste_as_rescript_jsx"; export const codeAnalysisWithReanalyze = ( targetDir: string | null, diff --git a/client/src/commands/paste_as_rescript_json.ts b/client/src/commands/paste_as_rescript_json.ts new file mode 100644 index 000000000..cf66e4019 --- /dev/null +++ b/client/src/commands/paste_as_rescript_json.ts @@ -0,0 +1,193 @@ +import { env, window, Position, Selection, TextDocument } from "vscode"; + +const INDENT_SIZE = 2; +const INDENT_UNIT = " ".repeat(INDENT_SIZE); + +const indent = (level: number) => INDENT_UNIT.repeat(level); + +const isLikelyJson = (text: string): boolean => { + const trimmed = text.trim(); + if (trimmed.length === 0) { + return false; + } + const first = trimmed[0]; + if (first === "{" || first === "[" || first === '"' || first === "-") { + return true; + } + if (first >= "0" && first <= "9") { + return true; + } + if ( + trimmed.startsWith("true") || + trimmed.startsWith("false") || + trimmed.startsWith("null") + ) { + return true; + } + return false; +}; + +const ensureFloatString = (value: number): string => { + const raw = Number.isFinite(value) ? String(value) : "0"; + if (raw.includes(".") || raw.includes("e") || raw.includes("E")) { + return raw; + } + return `${raw}.`; +}; + +const formatJsonValue = (value: unknown, level = 0): string => { + if (value === null) { + return "JSON.Null"; + } + + switch (typeof value) { + case "string": + return `JSON.String(${JSON.stringify(value)})`; + case "number": + return `JSON.Number(${ensureFloatString(value)})`; + case "boolean": + return `JSON.Boolean(${value})`; + case "object": + if (Array.isArray(value)) { + return formatArray(value, level); + } + return formatObject(value as Record, level); + default: + return "JSON.Null"; + } +}; + +const formatObject = ( + value: Record, + level: number, +): string => { + const entries = Object.entries(value); + if (entries.length === 0) { + return "JSON.Object(dict{})"; + } + const nextLevel = level + 1; + const lines = entries.map( + ([key, val]) => + `${indent(nextLevel)}${JSON.stringify(key)}: ${formatJsonValue( + val, + nextLevel, + )}`, + ); + return `JSON.Object(dict{\n${lines.join(",\n")}\n${indent(level)}})`; +}; + +const formatArray = (values: unknown[], level: number): string => { + if (values.length === 0) { + return "JSON.Array([])"; + } + const nextLevel = level + 1; + const lines = values.map( + (item) => `${indent(nextLevel)}${formatJsonValue(item, nextLevel)}`, + ); + return `JSON.Array([\n${lines.join(",\n")}\n${indent(level)}])`; +}; + +export type JsonConversionResult = + | { kind: "success"; formatted: string } + | { kind: "notJson" } + | { kind: "error"; errorMessage: string }; + +export const convertPlainTextToJsonT = (text: string): JsonConversionResult => { + if (!isLikelyJson(text)) { + return { kind: "notJson" }; + } + + try { + const parsed = JSON.parse(text); + return { kind: "success", formatted: formatJsonValue(parsed) }; + } catch { + return { + kind: "error", + errorMessage: "Clipboard JSON could not be parsed.", + }; + } +}; + +export const getBaseIndent = ( + document: TextDocument, + position: Position, +): string => { + const linePrefix = document + .lineAt(position) + .text.slice(0, position.character); + return /^\s*$/.test(linePrefix) ? linePrefix : ""; +}; + +export const applyBaseIndent = (formatted: string, baseIndent: string) => { + if (baseIndent.length === 0) { + return formatted; + } + + return formatted + .split("\n") + .map((line, index) => (index === 0 ? line : `${baseIndent}${line}`)) + .join("\n"); +}; + +export const buildInsertionText = ( + document: TextDocument, + position: Position, + formatted: string, +) => { + const baseIndent = getBaseIndent(document, position); + return applyBaseIndent(formatted, baseIndent); +}; + +const computeEndPosition = ( + insertionStart: Position, + indentedText: string, +): Position => { + const lines = indentedText.split("\n"); + if (lines.length === 1) { + return insertionStart.translate(0, lines[0].length); + } + return new Position( + insertionStart.line + lines.length - 1, + lines[lines.length - 1].length, + ); +}; + +export const pasteAsRescriptJson = async () => { + const editor = window.activeTextEditor; + if (!editor) { + window.showInformationMessage( + "No active editor to paste the ReScript JSON into.", + ); + return; + } + + const clipboardText = await env.clipboard.readText(); + const conversion = convertPlainTextToJsonT(clipboardText); + + if (conversion.kind === "notJson") { + window.showInformationMessage("Clipboard does not appear to contain JSON."); + return; + } + + if (conversion.kind === "error") { + window.showErrorMessage("Clipboard JSON could not be parsed."); + return; + } + + const formatted = conversion.formatted; + const selection = editor.selection; + const indentedText = buildInsertionText( + editor.document, + selection.start, + formatted, + ); + const insertionStart = selection.start; + const didEdit = await editor.edit((editBuilder) => { + editBuilder.replace(selection, indentedText); + }); + + if (didEdit) { + const endPosition = computeEndPosition(insertionStart, indentedText); + editor.selection = new Selection(endPosition, endPosition); + } +}; diff --git a/client/src/commands/paste_as_rescript_jsx.ts b/client/src/commands/paste_as_rescript_jsx.ts new file mode 100644 index 000000000..7e21744b3 --- /dev/null +++ b/client/src/commands/paste_as_rescript_jsx.ts @@ -0,0 +1,90 @@ +import { env, window, Position, Selection } from "vscode"; + +import { buildInsertionText } from "./paste_as_rescript_json"; +import { transformJsx } from "./transform-jsx"; + +export type JsxConversionResult = + | { kind: "success"; formatted: string } + | { kind: "empty" } + | { kind: "error"; errorMessage: string }; + +export const convertPlainTextToRescriptJsx = ( + text: string, +): JsxConversionResult => { + if (text.trim().length === 0) { + return { kind: "empty" }; + } + + try { + // If you ever need to fix a bug in transformJsx, + // please do so in https://github.com/nojaf/vanilla-jsx-to-rescript-jsx/blob/main/index.ts + // and then copy the changes to transform-jsx.ts + const formatted = transformJsx(text); + return { kind: "success", formatted }; + } catch (error) { + const errorMessage = + error instanceof Error ? error.message : "Unknown conversion error."; + return { + kind: "error", + errorMessage, + }; + } +}; + +const computeEndPosition = ( + insertionStart: Position, + indentedText: string, +): Position => { + const lines = indentedText.split("\n"); + if (lines.length === 1) { + return insertionStart.translate(0, lines[0].length); + } + return new Position( + insertionStart.line + lines.length - 1, + lines[lines.length - 1].length, + ); +}; + +export const pasteAsRescriptJsx = async () => { + const editor = window.activeTextEditor; + if (!editor) { + window.showInformationMessage( + "No active editor to paste the ReScript JSX into.", + ); + return; + } + + const clipboardText = await env.clipboard.readText(); + const conversion = convertPlainTextToRescriptJsx(clipboardText); + + if (conversion.kind === "empty") { + window.showInformationMessage( + "Clipboard does not appear to contain any JSX content.", + ); + return; + } + + if (conversion.kind === "error") { + window.showErrorMessage( + `Clipboard JSX could not be transformed: ${conversion.errorMessage}`, + ); + return; + } + + const formatted = conversion.formatted; + const selection = editor.selection; + const indentedText = buildInsertionText( + editor.document, + selection.start, + formatted, + ); + const insertionStart = selection.start; + const didEdit = await editor.edit((editBuilder) => { + editBuilder.replace(selection, indentedText); + }); + + if (didEdit) { + const endPosition = computeEndPosition(insertionStart, indentedText); + editor.selection = new Selection(endPosition, endPosition); + } +}; diff --git a/client/src/commands/transform-jsx.ts b/client/src/commands/transform-jsx.ts new file mode 100644 index 000000000..cb8c6a7df --- /dev/null +++ b/client/src/commands/transform-jsx.ts @@ -0,0 +1,190 @@ +import { parseSync, type Node } from "oxc-parser"; +import { walk } from "oxc-walker"; +import MagicString from "magic-string"; + +const integerRegex = /^-?\d+$/; +const floatRegex = /^-?\d+(\.\d+)?$/; + +const rescriptKeywords = new Set(["type", "open", "as", "in"]); + +type Rule = { + match: (node: T, parent: Node | null) => boolean; + transform: (node: T, parent: Node | null, magicString: MagicString) => void; + stopAfterMatch?: boolean; // If true, stop applying further rules after this one matches +}; + +// Single quotes to double quotes +const singleQuotesToDouble: Rule = { + match: (node) => + node.type === "JSXAttribute" && + node.value?.type === "Literal" && + typeof node.value.raw === "string" && + node.value.raw.startsWith("'"), + transform: (node, _, magicString) => { + const attr = node as Extract; + const value = attr.value as Extract; + magicString.update(value.start, value.end, `"${value.raw!.slice(1, -1)}"`); + }, +}; + +// SVG width/height numeric to string +const svgWidthHeightToString: Rule = { + match: (node, parent) => + node.type === "JSXAttribute" && + parent?.type === "JSXOpeningElement" && + parent.name.type === "JSXIdentifier" && + parent.name.name.toLowerCase() === "svg" && + node.name.type === "JSXIdentifier" && + (node.name.name === "width" || node.name.name === "height") && + node.value?.type === "JSXExpressionContainer" && + node.value.expression?.type === "Literal" && + typeof node.value.expression.value === "number", + transform: (node, _, magicString) => { + const attr = node as Extract; + const value = attr.value as Extract< + typeof attr.value, + { type: "JSXExpressionContainer" } + >; + const expression = value.expression as Extract< + typeof value.expression, + { type: "Literal" } + >; + const numericValue = String(expression.value); + magicString.update(value.start, value.end, `"${numericValue}"`); + }, +}; + +// Rescript keywords get underscore suffix +const rescriptKeywordUnderscore: Rule = { + match: (node) => + node.type === "JSXAttribute" && + node.name.type === "JSXIdentifier" && + rescriptKeywords.has(node.name.name), + transform: (node, _, magicString) => { + const attr = node as Extract; + magicString.appendRight(attr.name.end, "_"); + }, +}; + +// aria- attributes to camelCase +const ariaToCamelCase: Rule = { + match: (node) => + node.type === "JSXAttribute" && + node.name.type === "JSXIdentifier" && + typeof node.name.name === "string" && + node.name.name.startsWith("aria-"), + transform: (node, _, magicString) => { + const attr = node as Extract; + const name = attr.name.name as string; + magicString.update( + attr.name.start + 4, + attr.name.start + 6, + name[5]?.toUpperCase() || "", + ); + }, +}; + +// data-testid to dataTestId +const dataTestIdToCamelCase: Rule = { + match: (node) => + node.type === "JSXAttribute" && + node.name.type === "JSXIdentifier" && + node.name.name === "data-testid", + transform: (node, _, magicString) => { + const attr = node as Extract; + magicString.update(attr.name.start, attr.name.end, "dataTestId"); + }, +}; + +// Null values become =true +const nullValueToTrue: Rule = { + match: (node) => node.type === "JSXAttribute" && node.value === null, + transform: (node, _, magicString) => { + magicString.appendRight(node.end, "=true"); + }, +}; + +// Integer text nodes +const integerTextNode: Rule = { + match: (node) => + node.type === "JSXText" && + typeof node.raw === "string" && + integerRegex.test(node.raw.trim()), + transform: (node, _, magicString) => { + magicString.prependLeft(node.start, "{React.int("); + magicString.appendRight(node.end, ")}"); + }, + stopAfterMatch: true, +}; + +// Float text nodes +const floatTextNode: Rule = { + match: (node) => + node.type === "JSXText" && + typeof node.raw === "string" && + floatRegex.test(node.raw.trim()), + transform: (node, _, magicString) => { + magicString.prependLeft(node.start, "{React.float("); + magicString.appendRight(node.end, ")}"); + }, + stopAfterMatch: true, +}; + +// String text nodes +const stringTextNode: Rule = { + match: (node) => + node.type === "JSXText" && + typeof node.value === "string" && + node.value.trim() !== "", + transform: (node, _, magicString) => { + magicString.prependLeft(node.start, '{React.string("'); + magicString.appendRight(node.end, '")}'); + }, + stopAfterMatch: true, +}; + +const rules: Rule[] = [ + singleQuotesToDouble, + svgWidthHeightToString, + rescriptKeywordUnderscore, + ariaToCamelCase, + dataTestIdToCamelCase, + nullValueToTrue, + integerTextNode, + floatTextNode, + stringTextNode, +]; + +function applyRules( + node: Node, + parent: Node | null, + rules: Rule[], + magicString: MagicString, +): void { + for (const rule of rules) { + if (rule.match(node, parent)) { + rule.transform(node, parent, magicString); + if (rule.stopAfterMatch) { + break; + } + } + } +} + +export function transformJsx(input: string): string { + const magicString = new MagicString(input); + const parseResult = parseSync("clipboard-input.tsx", input, { + astType: "ts", + lang: "tsx", + }); + + walk(parseResult.program, { + enter: (node: Node, parent: Node | null) => { + applyRules(node, parent, rules, magicString); + }, + }); + + return magicString.toString(); +} + +export default transformJsx; diff --git a/client/src/extension.ts b/client/src/extension.ts index 9f17147a8..89fe28b80 100644 --- a/client/src/extension.ts +++ b/client/src/extension.ts @@ -13,6 +13,8 @@ import { WorkspaceEdit, CodeActionKind, Diagnostic, + DocumentDropOrPasteEditKind, + DocumentPasteEdit, } from "vscode"; import { ThemeColor } from "vscode"; @@ -29,6 +31,11 @@ import { DiagnosticsResultCodeActionsMap, statusBarItem, } from "./commands/code_analysis"; +import { + convertPlainTextToJsonT, + buildInsertionText, +} from "./commands/paste_as_rescript_json"; +import { convertPlainTextToRescriptJsx } from "./commands/paste_as_rescript_jsx"; let client: LanguageClient; @@ -387,6 +394,91 @@ export function activate(context: ExtensionContext) { customCommands.dumpDebugRetrigger(); }); + const pasteJsonEditKind = DocumentDropOrPasteEditKind.Text.append( + "rescript", + "json", + ); + const pasteJsxEditKind = DocumentDropOrPasteEditKind.Text.append( + "rescript", + "jsx", + ); + + context.subscriptions.push( + languages.registerDocumentPasteEditProvider( + { language: "rescript" }, + { + async provideDocumentPasteEdits( + document, + ranges, + dataTransfer, + _context, + token, + ) { + if (token.isCancellationRequested) { + return; + } + + const candidateItem = + dataTransfer.get("text/plain") ?? + dataTransfer.get("application/json"); + if (!candidateItem) { + return; + } + + const text = await candidateItem.asString(); + const targetRange = ranges[0]; + if (!targetRange) { + return; + } + + const edits: DocumentPasteEdit[] = []; + + const jsonConversion = convertPlainTextToJsonT(text); + if (jsonConversion.kind === "success") { + const insertText = buildInsertionText( + document, + targetRange.start, + jsonConversion.formatted, + ); + edits.push( + new DocumentPasteEdit( + insertText, + "Paste as ReScript JSON.t", + pasteJsonEditKind, + ), + ); + } + + const jsxConversion = convertPlainTextToRescriptJsx(text); + if (jsxConversion.kind === "success") { + const insertText = buildInsertionText( + document, + targetRange.start, + jsxConversion.formatted, + ); + edits.push( + new DocumentPasteEdit( + insertText, + "Paste as ReScript JSX", + pasteJsxEditKind, + ), + ); + } + + if (edits.length === 0) { + return; + } + + return edits; + }, + }, + { + providedPasteEditKinds: [pasteJsonEditKind, pasteJsxEditKind], + pasteMimeTypes: ["text/plain", "application/json"], + }, + ), + ); + commands.registerCommand( "rescript-vscode.go_to_location", async (fileUri: string, startLine: number, startCol: number) => { diff --git a/package-lock.json b/package-lock.json index 82495d5a2..199a17ae0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,18 +10,52 @@ "hasInstallScript": true, "license": "MIT", "dependencies": { + "magic-string": "^0.30.21", + "oxc-parser": "^0.97.0", + "oxc-walker": "^0.5.2", "semver": "^7.7.2" }, "devDependencies": { "@types/node": "^20.19.13", "@types/semver": "^7.7.0", - "@types/vscode": "1.68.0", + "@types/vscode": "1.97.0", "esbuild": "^0.20.1", "prettier": "^3.6.2", "typescript": "^5.8.3" }, "engines": { - "vscode": "^1.68.0" + "vscode": "^1.97.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.0.tgz", + "integrity": "sha512-pJdKGq/1iquWYtv1RRSljZklxHCOCAJFJrImO5ZLKPJVJlVUcs8yFwNQlqS0Lo8xT1VAXXTCZocF9n26FWEKsw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.1.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.0.tgz", + "integrity": "sha512-oAYoQnCYaQZKVS53Fq23ceWMRxq5EhQsE0x0RdQ55jT7wagMu5k+fS39v1fiSLrtrLQlXwVINenqhLMtTrV/1Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", + "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" } }, "node_modules/@esbuild/darwin-arm64": { @@ -40,6 +74,328 @@ "node": ">=12" } }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.7.tgz", + "integrity": "sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.5.0", + "@emnapi/runtime": "^1.5.0", + "@tybys/wasm-util": "^0.10.1" + } + }, + "node_modules/@oxc-parser/binding-android-arm64": { + "version": "0.97.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm64/-/binding-android-arm64-0.97.0.tgz", + "integrity": "sha512-oLCGuX+1zqTIUjTfCxiZO/Ad4p4wo2MksBSpjdgOC7htyfIg/Se9PK2xU2jzSXlIyzBivwK6AJFqJpcbzJlmsQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-darwin-arm64": { + "version": "0.97.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.97.0.tgz", + "integrity": "sha512-Rg7Yy0ICS4HiF+/ZcmjB7h67YOw23Iw06ETHP+0UHQkNuecFew9aDycGG62ohCb1/+QC5uVTW9naR4F8L3FndQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-darwin-x64": { + "version": "0.97.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-x64/-/binding-darwin-x64-0.97.0.tgz", + "integrity": "sha512-Kr2rgG7yEnv6ivreQtwKAetGeovfWMxsWzTPlM4BMkhI6jsj10BFN+tP5kUHrES66e7eaoFs0SNepHulCpofdw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-freebsd-x64": { + "version": "0.97.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-freebsd-x64/-/binding-freebsd-x64-0.97.0.tgz", + "integrity": "sha512-kAWTFHVR3KLcYQ7oEpRQV+WtEAPWZODQ/FsIVGVNAjzIfm9myuiLh7Kys8Vh3QwATPCuPg1w7FGexIm/A1a1lQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm-gnueabihf": { + "version": "0.97.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.97.0.tgz", + "integrity": "sha512-w4wYc5KRO6Mdxq9wXh6fAMuxB1LX7btj74+fTZG7/eP7ZiCTsxIM0GR4l7xQjRJOFd9rzlu7ZPq3LM7e9wmPTg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm-musleabihf": { + "version": "0.97.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.97.0.tgz", + "integrity": "sha512-DY+3aV2k9YyCRQ5/Zw83cG0xXvgnA6d31JSGfWkOAq9Aa22GeBE/NOzqqMw72HcxRKvYcJsCVpBwQaTICuBGIQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm64-gnu": { + "version": "0.97.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.97.0.tgz", + "integrity": "sha512-4B/H4CSc8LZSBTzQHMHQbbZww8B1qaQO+1iBxeKYo1LBD5ZAUZwgYCyM1VUPgqEfUY358a1/Nhn4RIwAbnEFWw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm64-musl": { + "version": "0.97.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.97.0.tgz", + "integrity": "sha512-Wg8ZPEXR3HHDlzvxqFH9XVc6xfnXaEjMmAuJ9priQmMin42O4B5TwvLmBNlW5Is30faKopGXiiH/Gjmcw/x4xg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-riscv64-gnu": { + "version": "0.97.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.97.0.tgz", + "integrity": "sha512-OJNHq6KGPdOh+YVk67T3MfRzLIy9MDMZCzH1f+xgh+kKPWzC4RqlqDNuoyqYiIxjO6kAVZZUQYvx4XVSKluJxw==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-s390x-gnu": { + "version": "0.97.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.97.0.tgz", + "integrity": "sha512-yZV1kKNzewd/lwWYBw6IRy7ckbduQsUt5LisM8NXt8T0Dg+jhkyy4y7M6X57/KyvT//vHCuRvpnwTr9lk1M9IA==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-x64-gnu": { + "version": "0.97.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.97.0.tgz", + "integrity": "sha512-Ck7cJMsZX19B0dvsl1v9a8VLeL9kEfUc0zMBjkgYmJfhVuINHcnZlQs8E5zTfD+dpP1wYzUhwgqv3o6hl9QaXA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-x64-musl": { + "version": "0.97.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.97.0.tgz", + "integrity": "sha512-COlEtnuyWfVjvylxhxoSd2HkAI85flvrQu3vGtt4Bm3+ZVdteFCNQskk3q8XfD0Cs+FdtnvDMbhApHyFKaEfsQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-wasm32-wasi": { + "version": "0.97.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-wasm32-wasi/-/binding-wasm32-wasi-0.97.0.tgz", + "integrity": "sha512-5Rt1uEe1VTw6aUluz8/nBNUbyCVGzwMJbXvPv+b4So+mFlkL+X2cTHb9LH8hcBgJ2TDITLT32J2TcV8Q8EPaKw==", + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^1.0.7" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@oxc-parser/binding-win32-arm64-msvc": { + "version": "0.97.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.97.0.tgz", + "integrity": "sha512-e2HDWO997STh7gADYJcjrZ+Fh5cSF8fwT6rRchNoV/hSwbJSC6ZpYFFFQEw2qZ2qyMeTmqQ6QVf7T9oKV18UXg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-win32-x64-msvc": { + "version": "0.97.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.97.0.tgz", + "integrity": "sha512-DQ92RUXw67ynu6fUzlFN/gr/rN3nxEQ35AC3EJYAgNKy/GFFJbNKGwFxGnmooje29XhBwibaRdxDs1OIgZBHvQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.97.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.97.0.tgz", + "integrity": "sha512-lxmZK4xFrdvU0yZiDwgVQTCvh2gHWBJCBk5ALsrtsBWhs0uDIi+FTOnXRQeQfs304imdvTdaakT/lqwQ8hkOXQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, "node_modules/@types/node": { "version": "20.19.13", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.13.tgz", @@ -58,10 +414,29 @@ "license": "MIT" }, "node_modules/@types/vscode": { - "version": "1.68.0", - "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.68.0.tgz", - "integrity": "sha512-duBwEK5ta/eBBMJMQ7ECMEsMvlE3XJdRGh3xoS1uOO4jl2Z4LPBl5vx8WvBP10ERAgDRmIt/FaSD4RHyBGbChw==", - "dev": true + "version": "1.97.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.97.0.tgz", + "integrity": "sha512-ueE73loeOTe7olaVyqP9mrRI54kVPJifUPjblZo9fYcv1CuVLPOEKEkqW0GkqPC454+nCEoigLWnC2Pp7prZ9w==", + "dev": true, + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "license": "MIT" }, "node_modules/esbuild": { "version": "0.20.1", @@ -101,6 +476,124 @@ "@esbuild/win32-x64": "0.20.1" } }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/magic-regexp": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/magic-regexp/-/magic-regexp-0.10.0.tgz", + "integrity": "sha512-Uly1Bu4lO1hwHUW0CQeSWuRtzCMNO00CmXtS8N6fyvB3B979GOEEeAkiTUDsmbYLAbvpUS/Kt5c4ibosAzVyVg==", + "license": "MIT", + "dependencies": { + "estree-walker": "^3.0.3", + "magic-string": "^0.30.12", + "mlly": "^1.7.2", + "regexp-tree": "^0.1.27", + "type-level-regexp": "~0.1.17", + "ufo": "^1.5.4", + "unplugin": "^2.0.0" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/mlly": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", + "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", + "license": "MIT", + "dependencies": { + "acorn": "^8.15.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.1" + } + }, + "node_modules/oxc-parser": { + "version": "0.97.0", + "resolved": "https://registry.npmjs.org/oxc-parser/-/oxc-parser-0.97.0.tgz", + "integrity": "sha512-gxUfidyxJY97BJ+JEN/PxiIxIU1Y1FAPyMTncgNymgd/Cb+TYprsXZqjVnVCmTUlIBoA1XVjbfP0+Iz+uAt7Ow==", + "license": "MIT", + "dependencies": { + "@oxc-project/types": "^0.97.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxc-parser/binding-android-arm64": "0.97.0", + "@oxc-parser/binding-darwin-arm64": "0.97.0", + "@oxc-parser/binding-darwin-x64": "0.97.0", + "@oxc-parser/binding-freebsd-x64": "0.97.0", + "@oxc-parser/binding-linux-arm-gnueabihf": "0.97.0", + "@oxc-parser/binding-linux-arm-musleabihf": "0.97.0", + "@oxc-parser/binding-linux-arm64-gnu": "0.97.0", + "@oxc-parser/binding-linux-arm64-musl": "0.97.0", + "@oxc-parser/binding-linux-riscv64-gnu": "0.97.0", + "@oxc-parser/binding-linux-s390x-gnu": "0.97.0", + "@oxc-parser/binding-linux-x64-gnu": "0.97.0", + "@oxc-parser/binding-linux-x64-musl": "0.97.0", + "@oxc-parser/binding-wasm32-wasi": "0.97.0", + "@oxc-parser/binding-win32-arm64-msvc": "0.97.0", + "@oxc-parser/binding-win32-x64-msvc": "0.97.0" + } + }, + "node_modules/oxc-walker": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/oxc-walker/-/oxc-walker-0.5.2.tgz", + "integrity": "sha512-XYoZqWwApSKUmSDEFeOKdy3Cdh95cOcSU8f7yskFWE4Rl3cfL5uwyY+EV7Brk9mdNLy+t5SseJajd6g7KncvlA==", + "license": "MIT", + "dependencies": { + "magic-regexp": "^0.10.0" + }, + "peerDependencies": { + "oxc-parser": ">=0.72.0" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "license": "MIT" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, "node_modules/prettier": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", @@ -117,6 +610,15 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/regexp-tree": { + "version": "0.1.27", + "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz", + "integrity": "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==", + "license": "MIT", + "bin": { + "regexp-tree": "bin/regexp-tree" + } + }, "node_modules/semver": { "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", @@ -129,6 +631,19 @@ "node": ">=10" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", + "optional": true + }, + "node_modules/type-level-regexp": { + "version": "0.1.17", + "resolved": "https://registry.npmjs.org/type-level-regexp/-/type-level-regexp-0.1.17.tgz", + "integrity": "sha512-wTk4DH3cxwk196uGLK/E9pE45aLfeKJacKmcEgEOA/q5dnPGNxXt0cfYdFxb57L+sEpf1oJH4Dnx/pnRcku9jg==", + "license": "MIT" + }, "node_modules/typescript": { "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", @@ -143,12 +658,39 @@ "node": ">=14.17" } }, + "node_modules/ufo": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", + "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", + "license": "MIT" + }, "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "dev": true, "license": "MIT" + }, + "node_modules/unplugin": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.10.tgz", + "integrity": "sha512-6NCPkv1ClwH+/BGE9QeoTIl09nuiAt0gS28nn1PvYXsGKRwM2TCbFA2QiilmehPDTXIe684k4rZI1yl3A1PCUw==", + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "acorn": "^8.15.0", + "picomatch": "^4.0.3", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "license": "MIT" } } } diff --git a/package.json b/package.json index 3ad3fcaff..75f610daf 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "language-server" ], "engines": { - "vscode": "^1.68.0" + "vscode": "^1.97.0" }, "activationEvents": [ "onLanguage:rescript" @@ -273,12 +273,15 @@ "devDependencies": { "@types/node": "^20.19.13", "@types/semver": "^7.7.0", - "@types/vscode": "1.68.0", + "@types/vscode": "1.97.0", "esbuild": "^0.20.1", "prettier": "^3.6.2", "typescript": "^5.8.3" }, "dependencies": { + "magic-string": "^0.30.21", + "oxc-parser": "^0.97.0", + "oxc-walker": "^0.5.2", "semver": "^7.7.2" }, "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"