diff --git a/rewrite-javascript/rewrite/package-lock.json b/rewrite-javascript/rewrite/package-lock.json index 0c787acc16..2c16d481b1 100644 --- a/rewrite-javascript/rewrite/package-lock.json +++ b/rewrite-javascript/rewrite/package-lock.json @@ -15,6 +15,7 @@ "diff": "^7.0.0", "immer": "^10.1.1", "picomatch": "^4.0.3", + "reflect-metadata": "^0.2.2", "tmp-promise": "^3.0.3", "typescript": "^5.8.3", "vscode-jsonrpc": "^8.2.1" @@ -85,6 +86,7 @@ "integrity": "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", @@ -2997,9 +2999,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "dev": true, "license": "MIT", "dependencies": { @@ -3526,6 +3528,12 @@ "dev": true, "license": "MIT" }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "license": "Apache-2.0" + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -3805,9 +3813,9 @@ } }, "node_modules/tmp": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", - "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", "license": "MIT", "engines": { "node": ">=14.14" diff --git a/rewrite-javascript/rewrite/package.json b/rewrite-javascript/rewrite/package.json index 7e089bba71..0a8154ccbb 100644 --- a/rewrite-javascript/rewrite/package.json +++ b/rewrite-javascript/rewrite/package.json @@ -36,6 +36,7 @@ "diff": "^7.0.0", "immer": "^10.1.1", "picomatch": "^4.0.3", + "reflect-metadata": "^0.2.2", "tmp-promise": "^3.0.3", "typescript": "^5.8.3", "vscode-jsonrpc": "^8.2.1" diff --git a/rewrite-javascript/rewrite/src/recipe.ts b/rewrite-javascript/rewrite/src/recipe.ts index 5cb5d429a3..04c239bf2f 100644 --- a/rewrite-javascript/rewrite/src/recipe.ts +++ b/rewrite-javascript/rewrite/src/recipe.ts @@ -18,6 +18,7 @@ import {Cursor, SourceFile, Tree} from "./tree"; import {ExecutionContext} from "./execution"; import {DataTableDescriptor} from "./data-table"; import {mapAsync} from "./util"; +import "reflect-metadata"; const OPTIONS_KEY = "__recipe_options__"; @@ -88,7 +89,7 @@ export abstract class Recipe { } async descriptor(): Promise { - const optionsRecord: Record = (this as any).constructor[OPTIONS_KEY] || {} + const optionsRecord: Record = (this as any).constructor[OPTIONS_KEY] || {} return { name: this.name, displayName: this.displayName, @@ -97,12 +98,39 @@ export abstract class Recipe { tags: this.tags, estimatedEffortPerOccurrence: this.estimatedEffortPerOccurrence, recipeList: await mapAsync(await this.recipeList(), async r => r.descriptor()), - options: Object.entries(optionsRecord).map(([key, descriptor]) => ({ - name: key, - value: (this as any)[key], - required: descriptor.required ?? true, - ...descriptor - })) + options: Object.entries(optionsRecord).map(([key, descriptor]) => { + var t = Reflect.getMetadata("design:type", this, key); + var simplifiedType = t?.name ?? "string"; + switch(t.name) { + case "String": { + simplifiedType = "string"; + break; + } + case "Boolean": { + simplifiedType = "boolean"; + break; + } + case "Number": { + simplifiedType = "number"; + break; + } + case "Symbol": { + simplifiedType = "symbol"; + break; + } + case "Object": { + simplifiedType = "object"; + break; + } + } + return { + name: key, + value: (this as any)[key], + required: descriptor.required ?? true, + type: simplifiedType, + ...descriptor + }; + }) } } @@ -138,7 +166,7 @@ export interface RecipeDescriptor { readonly options: ({ name: string, value?: any } & OptionDescriptor)[] } -export interface OptionDescriptor { +export interface OptionAnnotationDescriptor { readonly displayName: string readonly description: string readonly required?: boolean @@ -146,6 +174,10 @@ export interface OptionDescriptor { readonly valid?: string[] } +export interface OptionDescriptor extends OptionAnnotationDescriptor { + readonly type: string; +} + export abstract class ScanningRecipe

extends Recipe { private readonly recipeAccMessage = Symbol("org.openrewrite.recipe.acc"); @@ -217,7 +249,7 @@ export class RecipeRegistry { } } -export function Option(descriptor: OptionDescriptor) { +export function Option(descriptor: OptionAnnotationDescriptor) { return function (target: any, propertyKey: string) { // Ensure the constructor has options storage. if (!target.constructor.hasOwnProperty(OPTIONS_KEY)) { diff --git a/rewrite-javascript/rewrite/test/recipe.test.ts b/rewrite-javascript/rewrite/test/recipe.test.ts index de8e6b313b..6ed14ddbc3 100644 --- a/rewrite-javascript/rewrite/test/recipe.test.ts +++ b/rewrite-javascript/rewrite/test/recipe.test.ts @@ -40,7 +40,8 @@ describe("recipes", () => { displayName: "Text", name: "text", required: true, - value: undefined + value: undefined, + type: "string" } ], recipeList: [], diff --git a/rewrite-javascript/rewrite/tsconfig.json b/rewrite-javascript/rewrite/tsconfig.json index ff42c551f5..2b5fc9706e 100644 --- a/rewrite-javascript/rewrite/tsconfig.json +++ b/rewrite-javascript/rewrite/tsconfig.json @@ -13,7 +13,8 @@ "skipLibCheck": true, "rootDir": ".", "outDir": "./dist", - "experimentalDecorators": true + "experimentalDecorators": true, + "emitDecoratorMetadata": true }, "include": [ "src/**/*",