diff --git a/package-lock.json b/package-lock.json index 4c787fe..0006ed4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,11 +8,13 @@ "name": "slingr-vscode-extension", "version": "0.0.1", "dependencies": { + "pluralize": "^8.0.0", "ts-morph": "^26.0.0" }, "devDependencies": { "@types/mocha": "^10.0.10", "@types/node": "22.x", + "@types/pluralize": "^0.0.33", "@types/vscode": "^1.103.0", "@typescript-eslint/eslint-plugin": "^8.39.0", "@typescript-eslint/parser": "^8.39.0", @@ -471,6 +473,13 @@ "undici-types": "~6.21.0" } }, + "node_modules/@types/pluralize": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/@types/pluralize/-/pluralize-0.0.33.tgz", + "integrity": "sha512-JOqsl+ZoCpP4e8TDke9W79FDcSgPAR0l6pixx2JHkhnRjvShyYiAYw2LVsnA7K08Y6DeOnaU6ujmENO4os/cYg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/vscode": { "version": "1.103.0", "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.103.0.tgz", @@ -2624,6 +2633,15 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", diff --git a/package.json b/package.json index edd3d33..b25d245 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,14 @@ "command": "slingr-vscode-extension.changeFieldType", "title": "Change type" }, + { + "command": "slingr-vscode-extension.changeFieldToArray", + "title": "Change to array" + }, + { + "command": "slingr-vscode-extension.changeFieldToSingleValue", + "title": "Change to single value" + }, { "command": "slingr.runInfraUpdate", "title": "Run infrastructure update" @@ -187,19 +195,29 @@ }, { "command": "slingr-vscode-extension.renameField", - "when": "view == slingrExplorer && viewItem == 'field'", + "when": "view == slingrExplorer && viewItem =~ /field(Single|Array)/", "group": "0_modification@1" }, { "command": "slingr-vscode-extension.changeFieldType", - "when": "view == slingrExplorer && viewItem == 'field'", + "when": "view == slingrExplorer && viewItem =~ /field(Single|Array)/", "group": "0_modification@2" }, { - "command": "slingr-vscode-extension.deleteField", - "when": "view == slingrExplorer && viewItem == 'field'", + "command": "slingr-vscode-extension.changeFieldToArray", + "when": "view == slingrExplorer && viewItem == 'fieldSingle'", "group": "0_modification@3" }, + { + "command": "slingr-vscode-extension.changeFieldToSingleValue", + "when": "view == slingrExplorer && viewItem == 'fieldArray'", + "group": "0_modification@4" + }, + { + "command": "slingr-vscode-extension.deleteField", + "when": "view == slingrExplorer && viewItem =~ /field(Single|Array)/", + "group": "0_modification@5" + }, { "command": "slingr-vscode-extension.newFolder", "when": "view == slingrExplorer && viewItem == 'folder'", @@ -301,17 +319,27 @@ }, { "command": "slingr-vscode-extension.renameField", - "when": "view == slingrExplorer && viewItem == 'field'", + "when": "view == slingrExplorer && viewItem =~ /field(Single|Array)/", "group": "1_modification@4" }, { "command": "slingr-vscode-extension.changeFieldType", - "when": "view == slingrExplorer && viewItem == 'field'", + "when": "view == slingrExplorer && viewItem =~ /field(Single|Array)/", "group": "1_modification@5" }, + { + "command": "slingr-vscode-extension.changeFieldToArray", + "when": "view == slingrExplorer && viewItem == 'fieldSingle'", + "group": "1_modification@6" + }, + { + "command": "slingr-vscode-extension.changeFieldToSingleValue", + "when": "view == slingrExplorer && viewItem == 'fieldArray'", + "group": "1_modification@7" + }, { "command": "slingr-vscode-extension.deleteField", - "when": "view == slingrExplorer && viewItem == 'field'", + "when": "view == slingrExplorer && viewItem =~ /field(Single|Array)/", "group": "2_modification@3" } ] @@ -330,7 +358,7 @@ { "id": "slingrExplorer", "name": "Slingr Explorer", - "icon": "resources/slingr-icon.jpg" + "icon": "resources/slingr-icon.jpg" }, { "id": "slingrQuickInfo", @@ -352,6 +380,7 @@ "devDependencies": { "@types/mocha": "^10.0.10", "@types/node": "22.x", + "@types/pluralize": "^0.0.33", "@types/vscode": "^1.103.0", "@typescript-eslint/eslint-plugin": "^8.39.0", "@typescript-eslint/parser": "^8.39.0", @@ -361,6 +390,7 @@ "typescript": "^5.9.2" }, "dependencies": { + "pluralize": "^8.0.0", "ts-morph": "^26.0.0" } } diff --git a/src/cache/cache.ts b/src/cache/cache.ts index f553dab..885ac01 100644 --- a/src/cache/cache.ts +++ b/src/cache/cache.ts @@ -721,16 +721,16 @@ export class MetadataCache { /** * Gets a clean, human-readable name for a ts-morph Type object. * This method correctly handles imported types, removing the "import(...)" part, - * and properly extracts element types from arrays. + * and preserves array notation for array types. * @param type The ts-morph Type object. * @returns The clean type name as a string. */ private getCleanTypeName(type: Type): string { - // Handle array types by extracting the element type + // Handle array types by preserving the array notation if (type.isArray()) { const elementType = type.getArrayElementType(); if (elementType) { - return this.getCleanTypeName(elementType); + return this.getCleanTypeName(elementType) + '[]'; } } diff --git a/src/explorer/appTreeItem.ts b/src/explorer/appTreeItem.ts index 3ff4034..ddab760 100644 --- a/src/explorer/appTreeItem.ts +++ b/src/explorer/appTreeItem.ts @@ -14,9 +14,19 @@ export class AppTreeItem extends vscode.TreeItem { folderPath?: string ) { super(label, collapsibleState); - this.contextValue = itemType; this.folderPath = folderPath; + let finalContextValue = itemType; + if (itemType === 'field' && metadata && 'type' in metadata) { + const propMetadata = metadata as PropertyMetadata; + if (propMetadata.type.endsWith('[]')) { + finalContextValue = 'fieldArray'; + } else { + finalContextValue = 'fieldSingle'; + } + } + this.contextValue = finalContextValue; + // Icon logic if (!this.extensionUri) { console.warn(`[MyTreeItem] Extension URI not provided for item: "${label}". Local icons will not be loaded.`); @@ -47,6 +57,8 @@ export class AppTreeItem extends vscode.TreeItem { iconFileName = "database.svg"; break; case "field": + case "fieldSingle": + case "fieldArray": iconFileName = "field.svg"; break; case "modelActionsFolder": diff --git a/src/refactor/refactorDisposables.ts b/src/refactor/refactorDisposables.ts index afc1f3e..5801211 100644 --- a/src/refactor/refactorDisposables.ts +++ b/src/refactor/refactorDisposables.ts @@ -15,6 +15,8 @@ import { PropertyMetadata } from '../cache/cache'; import { fieldTypeConfig } from '../utils/fieldTypes'; import { RenameDataSourceTool } from './tools/renameDataSource'; import { DeleteDataSourceTool } from './tools/deleteDataSource'; +import { ChangeFieldToArrayTool } from './tools/changeFieldToArray'; +import { ChangeFieldToSingleValueTool } from './tools/changeFieldToSingleValue'; /** * Returns an array of all available refactor tools for the application. @@ -38,6 +40,8 @@ export function getAllRefactorTools(): IRefactorTool[] { new RenameFieldTool(), new DeleteFieldTool(), new ChangeFieldTypeTool(), + new ChangeFieldToArrayTool(), + new ChangeFieldToSingleValueTool(), new AddDecoratorTool(), new RenameDataSourceTool(), new DeleteDataSourceTool(), diff --git a/src/refactor/refactorInterfaces.ts b/src/refactor/refactorInterfaces.ts index 1383504..5e13ce1 100644 --- a/src/refactor/refactorInterfaces.ts +++ b/src/refactor/refactorInterfaces.ts @@ -67,6 +67,16 @@ export interface DeleteDataSourcePayload extends BasePayload { urisToDelete: vscode.Uri[]; } +export interface ChangeFieldToArrayPayload extends BasePayload { + field: PropertyMetadata; + modelName: string; +} + +export interface ChangeFieldToSingleValuePayload extends BasePayload { + field: PropertyMetadata; + modelName: string; +} + // --- Discriminated Union for ChangeObject --- /** @@ -82,6 +92,8 @@ export type ChangePayloadMap = { 'ADD_DECORATOR': AddDecoratorPayload; 'RENAME_DATA_SOURCE': RenameDataSourcePayload; 'DELETE_DATA_SOURCE': DeleteDataSourcePayload; + 'CHANGE_FIELD_TO_ARRAY': ChangeFieldToArrayPayload; + 'CHANGE_FIELD_TO_SINGLE_VALUE': ChangeFieldToSingleValuePayload; }; /** diff --git a/src/refactor/tools/changeFieldToArray.ts b/src/refactor/tools/changeFieldToArray.ts new file mode 100644 index 0000000..4977bc3 --- /dev/null +++ b/src/refactor/tools/changeFieldToArray.ts @@ -0,0 +1,222 @@ +import * as vscode from 'vscode'; +import { ChangeObject, IRefactorTool, ManualRefactorContext, ChangeFieldToArrayPayload, RenameModelPayload, RenameFieldPayload } from '../refactorInterfaces'; +import { FileMetadata, MetadataCache, PropertyMetadata } from '../../cache/cache'; +import { isModelFile, isField, areRangesEqual, isFieldMultiple, isModel } from '../../utils/metadata'; +import pluralize from 'pluralize'; + +/** + * Tool to change a field from a single value type to an array type. + * E.g., changes `tag: string` to `tags: string[]` and updates all references. + */ +export class ChangeFieldToArrayTool implements IRefactorTool { + getCommandId(): string { + return 'slingr-vscode-extension.changeFieldToArray'; + } + + getTitle(): string { + return 'Change to array'; + } + + getHandledChangeTypes(): string[] { + return ['CHANGE_FIELD_TO_ARRAY']; + } + + async canHandleManualTrigger(context: ManualRefactorContext): Promise { + if (!context.metadata) { + return false; + } + if (isModelFile(context.uri) && isField(context.metadata)) { + const field = context.metadata as PropertyMetadata; + return !isFieldMultiple(field); + } + return false; + } + + analyze(oldFileMeta?: FileMetadata, newFileMeta?: FileMetadata, accumulatedChanges: ChangeObject[] = []): ChangeObject[] { + const changes: ChangeObject[] = []; + if (!oldFileMeta || !newFileMeta || !isModelFile(newFileMeta.uri)) { + return []; + } + + const classRenames = new Map(); + const fieldRenamesByClass = new Map>(); + for (const change of accumulatedChanges) { + if (change.type === 'RENAME_MODEL') { + const payload = change.payload as RenameModelPayload; + classRenames.set(payload.oldName, payload.newName); + } + if (change.type === 'RENAME_FIELD') { + const payload = change.payload as RenameFieldPayload; + if (!fieldRenamesByClass.has(payload.modelName)) { + fieldRenamesByClass.set(payload.modelName, new Map()); + } + fieldRenamesByClass.get(payload.modelName)!.set(payload.oldName, payload.newName); + } + } + + for (const oldClassName in oldFileMeta.classes) { + const oldClass = oldFileMeta.classes[oldClassName]; + const newClassName = classRenames.get(oldClassName) || oldClassName; + const newClass = newFileMeta.classes[newClassName]; + + if (!newClass || !isModel(oldClass) || !isModel(newClass)) { + continue; + } + + const fieldRenames = fieldRenamesByClass.get(oldClassName) || new Map(); + for (const oldPropName in oldClass.properties) { + const oldProp = oldClass.properties[oldPropName]; + const newPropName = fieldRenames.get(oldPropName) || oldPropName; + const newProp = newClass.properties[newPropName]; + + if (!newProp || !isField(oldProp) || !isField(newProp)) { + continue; + } + + const oldType = oldProp.type; + const newType = newProp.type; + + if (!oldType.endsWith('[]') && newType === oldType + '[]') { + const payload: ChangeFieldToArrayPayload = { + field: oldProp, + modelName: newClassName, + isManual: false, + }; + changes.push({ + type: 'CHANGE_FIELD_TO_ARRAY', + uri: newFileMeta.uri, + description: `Field '${newProp.name}' in Model '${newClassName}' changed to an array.`, + payload, + }); + } + } + } + + return changes; + } + + async initiateManualRefactor(context: ManualRefactorContext): Promise { + if (!context.metadata || !isField(context.metadata)) { + return undefined; + } + const field = context.metadata as PropertyMetadata; + + // Find the containing model's name for context + const fileMetadata = context.cache.getMetadataForFile(context.uri.fsPath); + let modelName = 'UnknownModel'; + if (fileMetadata) { + for (const [className, classData] of Object.entries(fileMetadata.classes)) { + if (classData.properties[field.name]) { + modelName = className; + break; + } + } + } + + const payload: ChangeFieldToArrayPayload = { + field: context.metadata as PropertyMetadata, + modelName, + isManual: true, + }; + + return { + type: 'CHANGE_FIELD_TO_ARRAY', + uri: context.uri, + description: `Change field '${payload.field.name}' to array.`, + payload, + }; + } + + async prepareEdit(change: ChangeObject, cache: MetadataCache): Promise { + if (change.type !== 'CHANGE_FIELD_TO_ARRAY') { + throw new Error(`ChangeFieldToArrayTool can only handle CHANGE_FIELD_TO_ARRAY changes, received: ${change.type}`); + } + + const { field } = change.payload; + const workspaceEdit = new vscode.WorkspaceEdit(); + const { declaration, references = [] } = field; + + if (change.payload.isManual) { + // Change the field's type to an array type + const document = await vscode.workspace.openTextDocument(declaration.uri); + const lineText = document.lineAt(declaration.range.start.line).text; + const typeRegex = new RegExp(`:\\s*${field.type}`); + const match = lineText.match(typeRegex); + + if (match && typeof match.index === 'number') { + const startPos = new vscode.Position(declaration.range.start.line, match.index); + const typeNodeText = match[0]; + const endPos = startPos.translate(0, typeNodeText.length); + const typeRange = new vscode.Range(startPos, endPos); + workspaceEdit.replace(declaration.uri, typeRange, `: ${field.type}[]`); + } + } + + // 2. Pluralize name if it's not already plural and update all references + const newName = pluralize.plural(field.name); + if (newName !== field.name) { + // Update the declaration + workspaceEdit.replace(declaration.uri, declaration.range, newName); + + // Update all other references + for (const ref of references) { + // Skip the declaration itself as we just handled it + if (ref.uri.fsPath === declaration.uri.fsPath && areRangesEqual(ref.range, declaration.range)) { + continue; + } + workspaceEdit.replace(ref.uri, ref.range, newName); + } + } + + return workspaceEdit; + } + + async executePrompt(change: ChangeObject): Promise { + if (change.type !== 'CHANGE_FIELD_TO_ARRAY') return; + + const { field, modelName } = change.payload; + const oldName = field.name; + const newName = oldName.endsWith('s') ? oldName : `${oldName}s`; + const oldType = field.type; + const newType = `${oldType}[]`; + const referenceLocations = field.references + .map(ref => `- \`${ref.uri.fsPath.split('/').pop()}\` at line ${ref.range.start.line + 1}`) + .join('\n'); + + const prompt = `## Field Refactoring - Code Review Required + +The field **\`${oldName}\`** in the model **\`${modelName}\`** has been refactored to be an array. + +**Changes Applied:** +- **Name Change:** \`${oldName}\` -> \`${newName}\` +- **Type Change:** \`${oldType}\` -> \`${newType}\` +- All direct references to the field name have been updated. + +**Problem:** The logic using this field might now be incorrect. For example, code that treated it as a single value (e.g., \`record.${newName} === 'value'\`) will now need to handle an array (e.g., \`record.${newName}.includes('value')\`). + +**Your Task:** Help me review and fix the code that uses this field. + +### Instructions: + +1. **Analyze the following locations** where the field was referenced. The logic in these places is likely broken. +2. **For each location, suggest the necessary code changes** to correctly handle the new array type. +3. **Provide clear before/after code snippets** and explain why the change is needed. + +### Reference Locations to Check: +${referenceLocations} + +### Common Patterns to Fix: +- **Direct comparisons:** Change \`===, !==\` to \`.includes()\` or loops. +- **Assignments:** Ensure an array is being assigned, not a single value. +- **UI Display:** Adjust how the field is rendered to show a list or tags. +- **Function arguments:** Update functions that expected a single value. + +Please start by analyzing the first reference location.`; + + try { + await vscode.commands.executeCommand('workbench.action.chat.open', prompt); + } catch (error) { + console.error('Failed to open chat with custom prompt:', error); + } + } +} \ No newline at end of file diff --git a/src/refactor/tools/changeFieldToSingleValue.ts b/src/refactor/tools/changeFieldToSingleValue.ts new file mode 100644 index 0000000..d7758e7 --- /dev/null +++ b/src/refactor/tools/changeFieldToSingleValue.ts @@ -0,0 +1,231 @@ +import * as vscode from 'vscode'; +import { ChangeObject, IRefactorTool, ManualRefactorContext, ChangeFieldToSingleValuePayload, RenameFieldPayload, RenameModelPayload } from '../refactorInterfaces'; +import { FileMetadata, MetadataCache, PropertyMetadata } from '../../cache/cache'; +import { isModelFile, isField, areRangesEqual, isFieldMultiple, isModel } from '../../utils/metadata'; +import pluralize from 'pluralize'; + +/** + * Tool to change a field from an array type to a single value type. + * E.g., changes `tags: string[]` to `tag: string` and updates all references. + */ +export class ChangeFieldToSingleValueTool implements IRefactorTool { + getCommandId(): string { + return 'slingr-vscode-extension.changeFieldToSingleValue'; + } + + getTitle(): string { + return 'Change to single value'; + } + + getHandledChangeTypes(): string[] { + return ['CHANGE_FIELD_TO_SINGLE_VALUE']; + } + + async canHandleManualTrigger(context: ManualRefactorContext): Promise { + if (!context.metadata) { + return false; + } + if (isModelFile(context.uri) && isField(context.metadata)) { + const field = context.metadata as PropertyMetadata; + // A field can be changed to a single value if its type ends with '[]' + return isFieldMultiple(field); + } + return false; + } + + analyze(oldFileMeta?: FileMetadata, newFileMeta?: FileMetadata, accumulatedChanges: ChangeObject[] = []): ChangeObject[] { + const changes: ChangeObject[] = []; + if (!oldFileMeta || !newFileMeta || !isModelFile(newFileMeta.uri)) { + return []; + } + + const classRenames = new Map(); + const fieldRenamesByClass = new Map>(); + for (const change of accumulatedChanges) { + if (change.type === 'RENAME_MODEL') { + const payload = change.payload as RenameModelPayload; + classRenames.set(payload.oldName, payload.newName); + } + if (change.type === 'RENAME_FIELD') { + const payload = change.payload as RenameFieldPayload; + if (!fieldRenamesByClass.has(payload.modelName)) { + fieldRenamesByClass.set(payload.modelName, new Map()); + } + fieldRenamesByClass.get(payload.modelName)!.set(payload.oldName, payload.newName); + } + } + + for (const oldClassName in oldFileMeta.classes) { + const oldClass = oldFileMeta.classes[oldClassName]; + const newClassName = classRenames.get(oldClassName) || oldClassName; + const newClass = newFileMeta.classes[newClassName]; + + if (!newClass || !isModel(oldClass) || !isModel(newClass)) { + continue; + } + + const fieldRenames = fieldRenamesByClass.get(oldClassName) || new Map(); + for (const oldPropName in oldClass.properties) { + const oldProp = oldClass.properties[oldPropName]; + const newPropName = fieldRenames.get(oldPropName) || oldPropName; + const newProp = newClass.properties[newPropName]; + + if (!newProp || !isField(oldProp) || !isField(newProp)) { + continue; + } + + const oldType = oldProp.type; + const newType = newProp.type; + + if (oldType.endsWith('[]') && oldType === newType + '[]') { + const payload: ChangeFieldToSingleValuePayload = { + field: oldProp, + modelName: newClassName, + isManual: false, + }; + changes.push({ + type: 'CHANGE_FIELD_TO_SINGLE_VALUE', + uri: newFileMeta.uri, + description: `Field '${newProp.name}' in Model '${newClassName}' changed to a single value.`, + payload, + }); + } + } + } + + return changes; + } + + async initiateManualRefactor(context: ManualRefactorContext): Promise { + if (!context.metadata || !isField(context.metadata)) { + return undefined; + } + const field = context.metadata as PropertyMetadata; + + // Find the containing model's name for context + const fileMetadata = context.cache.getMetadataForFile(context.uri.fsPath); + let modelName = 'UnknownModel'; + if (fileMetadata) { + for (const [className, classData] of Object.entries(fileMetadata.classes)) { + if (classData.properties[field.name]) { + modelName = className; + break; + } + } + } + + const payload: ChangeFieldToSingleValuePayload = { + field: context.metadata as PropertyMetadata, + modelName, + isManual: true, + }; + + return { + type: 'CHANGE_FIELD_TO_SINGLE_VALUE', + uri: context.uri, + description: `Change field '${payload.field.name}' to single value.`, + payload, + }; + } + + async prepareEdit(change: ChangeObject, cache: MetadataCache): Promise { + if (change.type !== 'CHANGE_FIELD_TO_SINGLE_VALUE') { + throw new Error(`ChangeFieldToSingleValueTool can only handle CHANGE_FIELD_TO_SINGLE_VALUE changes, received: ${change.type}`); + } + + const { field } = change.payload; + const workspaceEdit = new vscode.WorkspaceEdit(); + const { declaration, references = [] } = field; + + if (change.payload.isManual) { + // Change type from an array to a single value + const newType = field.type.replace('[]', ''); + const document = await vscode.workspace.openTextDocument(declaration.uri); + const lineText = document.lineAt(declaration.range.start.line).text; + + // Helper to escape special characters for use in a regular expression. + const escapeRegExp = (str: string) => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + + // Build a robust regex that captures the base type and the array brackets separately. + const typeRegex = new RegExp(`(:\\s*${escapeRegExp(newType)})(\\[\\])`); + const match = lineText.match(typeRegex); + + if (match && typeof match.index === 'number') { + // The full text that was matched, e.g., ": string[]" + const fullMatchedText = match[0]; + const replacementText = match[1]; // e.g., ": string" + const startPos = new vscode.Position(declaration.range.start.line, match.index); + const endPos = startPos.translate(0, fullMatchedText.length); + const typeRange = new vscode.Range(startPos, endPos); + + workspaceEdit.replace(declaration.uri, typeRange, replacementText); + } + } + + // Singularize name if it's plural and update all references + const newName = pluralize.singular(field.name); + if (newName !== field.name) { + // Update the declaration + workspaceEdit.replace(declaration.uri, declaration.range, newName); + + for (const ref of references) { + // Skip the declaration itself + if (ref.uri.fsPath === declaration.uri.fsPath && areRangesEqual(ref.range, declaration.range)) { + continue; + } + workspaceEdit.replace(ref.uri, ref.range, newName); + } + } + + return workspaceEdit; +} + + async executePrompt(change: ChangeObject): Promise { + if (change.type !== 'CHANGE_FIELD_TO_SINGLE_VALUE') return; + + const { field, modelName } = change.payload; + const oldName = field.name; + const newName = oldName.endsWith('s') ? oldName.slice(0, -1) : oldName; + const oldType = field.type; + const newType = oldType.replace('[]', ''); + const referenceLocations = field.references + .map(ref => `- \`${ref.uri.fsPath.split('/').pop()}\` at line ${ref.range.start.line + 1}`) + .join('\n'); + + const prompt = `## Field Refactoring - Code Review Required + +The field **\`${oldName}\`** in the model **\`${modelName}\`** has been refactored to be a single value. + +**Changes Applied:** +- **Name Change:** \`${oldName}\` -> \`${newName}\` +- **Type Change:** \`${oldType}[]\` -> \`${newType}\` +- All direct references to the field name have been updated. + +**Problem:** The logic using this field might now be incorrect. Code that treated it as an array (e.g., \`record.${newName}.push('value')\`) will now need to handle a single value (e.g., \`record.${newName} = 'value'\`). This could also affect how you handle null or undefined values. + +**Your Task:** Help me review and fix the code that uses this field. + +### Instructions: + +1. **Analyze the following locations** where the field was referenced. The logic in these places is likely broken. +2. **For each location, suggest the necessary code changes** to correctly handle the new single-value type. You might need to decide which element of the former array to use (e.g., the first one) or how to handle cases where the array was empty. +3. **Provide clear before/after code snippets** and explain your reasoning. + +### Reference Locations to Check: +${referenceLocations} + +### Common Patterns to Fix: +- **Array methods:** Replace \`.push()\`, \`.includes()\`, \`.map()\`, etc., with direct assignment or comparison. +- **Loops:** Remove loops that iterated over the field. +- **Assignments:** Ensure a single value is being assigned, not an array. +- **UI Display:** Adjust UI components that expected a list. + +Please start by analyzing the first reference location, paying close attention to how to resolve the array-to-single-value logic.`; + + try { + await vscode.commands.executeCommand('workbench.action.chat.open', prompt); + } catch (error) { + console.error('Failed to open chat with custom prompt:', error); + } + } +} \ No newline at end of file diff --git a/src/utils/metadata.ts b/src/utils/metadata.ts index da7c83e..c5e4169 100644 --- a/src/utils/metadata.ts +++ b/src/utils/metadata.ts @@ -46,6 +46,15 @@ export function isField(metadata: DecoratedClass | PropertyMetadata | DataSource return 'type' in metadata && metadata.decorators.some(d => fieldDecoratorNames.includes(d.name) || d.name === 'Field'); } +/** + * Checks if a field property has a multiple (array) type. + * @param metadata - The property metadata to check. + * @returns True if the field type is an array (e.g., 'string[]'), false otherwise. + */ +export function isFieldMultiple(metadata: PropertyMetadata): boolean { + return metadata.type.endsWith('[]'); +} + export function isMethodMetadata(value: any): value is MethodMetadata { // Check for properties that uniquely identify a MethodMetadata object return typeof value === 'object' && value !== null && 'parameters' in value && 'declaration' in value;