diff --git a/.gitignore b/.gitignore index 077b7b4..d45ca98 100644 --- a/.gitignore +++ b/.gitignore @@ -131,3 +131,4 @@ dist **/.vs/ **/bin **/obj +pulltointerfacor-1.0.0.vsix diff --git a/.vscodeignore b/.vscodeignore index 72aa0fe..33348a1 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -1,6 +1,7 @@ .vscode/** .vscode-test/** src/** +.github/** .gitignore .yarnrc vsc-extension-quickstart.md @@ -9,3 +10,5 @@ vsc-extension-quickstart.md **/*.map **/*.ts **/.vscode-test.* +**/Sample/** +**/GifInstruction/** diff --git a/CHANGELOG.md b/CHANGELOG.md index 053ec52..9aa9c8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,6 @@ All notable changes to the "pulltointerfacor" extension will be documented in th Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. -## [Unreleased] +## [1.0.0] 1.19.2024 -- Initial release \ No newline at end of file +- Initial release diff --git a/GifInstruction/PullToExample.gif b/GifInstruction/PullToExample.gif new file mode 100644 index 0000000..de17ae4 Binary files /dev/null and b/GifInstruction/PullToExample.gif differ diff --git a/PullToInterfacor.png b/PullToInterfacor.png new file mode 100644 index 0000000..50ef3d3 Binary files /dev/null and b/PullToInterfacor.png differ diff --git a/README.md b/README.md index 7bddd80..33a9390 100644 --- a/README.md +++ b/README.md @@ -1,74 +1,73 @@ -# pulltointerfacor README +# PullToInterfacor -This is the README for your extension "pulltointerfacor". After writing up a brief description, we recommend including the following sections. - -## Features - -Describe specific features of your extension including screenshots of your extension in action. Image paths are relative to this README file. - -For example if there is an image subfolder under your extension project workspace: - -\!\[feature X\]\(images/feature-x.png\) - -> Tip: Many popular extensions utilize animations. This is an excellent way to show off your extension! We recommend short, focused animations that are easy to follow. - -## Requirements - -If you have any requirements or dependencies, add a section describing those and how to install and configure them. +--- +![GitHub CI](https://github.com/d1820/PullToInterfacor/actions/workflows/node.js.yml/badge.svg) +![GitHub License](https://img.shields.io/github/license/d1820/PullToInterfacor?logo=github&logoColor=green) +![Visual Studio Marketplace Rating](https://img.shields.io/visual-studio-marketplace/stars/DanTurco.PullToInterfacor) +![Visual Studio Marketplace Downloads](https://img.shields.io/visual-studio-marketplace/d/DanTurco.PullToInterfacor) +![Visual Studio Marketplace Version (including pre-releases)](https://img.shields.io/visual-studio-marketplace/v/DanTurco.PullToInterfacor) -## Extension Settings -Include if your extension adds any VS Code settings through the `contributes.configuration` extension point. +A Visual Studio Code Extension to include the ability to **Pull** methods and properties to inherited interfaces and base classes. This is targeted to C# development and is meant as a supplemental extension to C# Dev Kit. This extension supports pulling public properties and methods to interfaces, and public and protected methods to base classes. -For example: +## Installation +--- -This extension contributes the following settings: + -* `myExtension.enable`: Enable/disable this extension. -* `myExtension.thing`: Set to `blah` to do something. +## Table of Contents -## Known Issues + -Calling out known issues can help limit users opening duplicate issues against your extension. +- [PullToInterfacor](#pulltointerfacor) + - [Installation](#installation) + - [Table of Contents](#table-of-contents) + - [Instruction](#instruction) + - [Caveats](#caveats) + - [Known Issues](#known-issues) + - [Supported Members](#supported-members) + - [Interfaces](#interfaces) + - [Base Classes](#base-classes) + - [Usage Examples](#usage-examples) + - [Special Thanks](#special-thanks) -## Release Notes + -Users appreciate release notes as you update your extension. +## Instruction +--- -### 1.0.0 +1. Once installed successful to Visual Studio Code. You can access the commands from F1 then search for **Pull To**. -Initial release of ... +## Caveats -### 1.0.1 +- This extension only works for base classes and interfaces that have their own C# file. Having multiple interfaces defined in 1 file will not work. +- Interface files must following the convention **I**Name. The I is the only way it knows its an interface with doing a bunch of file parsing and inference. To keep it fast I went with convention. +- This is a lot of file parsing to determine what to move, that said the easiest was to ensure all the using are present were to copy them all from the main class to the base or interface, and deduplicate the list. With C# Dev Kit installed the cleanup from that is quick to remove unused using. +- When pulling full backed property in this version we do not move the private backed field, so if using full backed property you will need to move that yourself. I am open to accepting PRs to update that functionality 😁 -Fixed issue #. -### 1.1.0 +## Known Issues -Added features X, Y, and Z. ---- +## Supported Members -## Following extension guidelines +### Interfaces -Ensure that you've read through the extensions guidelines and follow the best practices for creating your extension. +- public properties +- public methods -* [Extension Guidelines](https://code.visualstudio.com/api/references/extension-guidelines) +### Base Classes -## Working with Markdown +- public/protected properties +- public/protected methods -You can author your README using Visual Studio Code. Here are some useful editor keyboard shortcuts: -* Split the editor (`Cmd+\` on macOS or `Ctrl+\` on Windows and Linux). -* Toggle preview (`Shift+Cmd+V` on macOS or `Shift+Ctrl+V` on Windows and Linux). -* Press `Ctrl+Space` (Windows, Linux, macOS) to see a list of Markdown snippets. +## Usage Examples -## For more information -* [Visual Studio Code's Markdown Support](http://code.visualstudio.com/docs/languages/markdown) -* [Markdown Syntax Reference](https://help.github.com/articles/markdown-basics/) -**Enjoy!** + -https://github.com/devshop/csharp-model-to-builder +## Special Thanks +Some of the helpers were adopted from [devshop](https://github.com/devshop/csharp-model-to-builder) diff --git a/Sample/SampleProject/BaseClass.cs b/Sample/SampleProject/BaseClass.cs index 0c44d6e..a756af0 100644 --- a/Sample/SampleProject/BaseClass.cs +++ b/Sample/SampleProject/BaseClass.cs @@ -8,6 +8,17 @@ namespace Sample { public class BaseClass : IBaseClass { - + public string FullPropertyAlt + { + get + { + return _fullProperty; + } + set + { + _fullProperty = value; + } + } + public int MyProperty { get; set; } } } diff --git a/Sample/SampleProject/IInternalTest.cs b/Sample/SampleProject/IInternalTest.cs index 865b4ad..2fed0c8 100644 --- a/Sample/SampleProject/IInternalTest.cs +++ b/Sample/SampleProject/IInternalTest.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using SampleProject; namespace Sample { diff --git a/Sample/SampleProject/IMyClass.cs b/Sample/SampleProject/IMyClass.cs index 17b307d..3320012 100644 --- a/Sample/SampleProject/IMyClass.cs +++ b/Sample/SampleProject/IMyClass.cs @@ -2,11 +2,13 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using Test; +using SampleProject; + namespace Sample { public interface IMyClass { - string FullPropertyAlt { get; set; } + async Task GetNewIdAsync(string name,string address,string city,string state) where TNewType : class; + string FullProperty { get; set; } } } diff --git a/Sample/SampleProject/MyClass.cs b/Sample/SampleProject/MyClass.cs index 2a778eb..af15646 100644 --- a/Sample/SampleProject/MyClass.cs +++ b/Sample/SampleProject/MyClass.cs @@ -9,28 +9,16 @@ namespace Sample public class MyClass : BaseClass, IMyClass, IMyTypedClass where TType : class { private string _fullProperty; - public int MyProperty { get; set; } public int MyPropertyLamda => 5; public string FullProperty { get => _fullProperty; set => _fullProperty = value; } - public string FullPropertyAlt - { - get - { - return _fullProperty; - } - set - { - _fullProperty = value; - } - } public async Task GetNewIdAsync(string name, string address, string city, - string state) where TNewType : TType + string state) where TNewType : class { Console.WriteLine("starting"); var coll = new List(); @@ -38,22 +26,18 @@ public async Task GetNewIdAsync(string name, { foreach (var item in coll) { - } } Console.WriteLine("ending"); return 1; } - public Address MethodLambdaMultiLine() => new Address { Name = "", City = "", Street = "" }; - public int MyMethodLamda() => 5; - protected async Task GetProtectedAsync(string name, string address) where TNewType : class { diff --git a/package.json b/package.json index 299bae1..9b9905c 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,25 @@ { "name": "pulltointerfacor", + "publisher": "DanTurco", "displayName": "PullToInterfacor", - "description": "", - "version": "0.0.1", + "description": "A Visual Studio Code Extension to include the ability to **Pull** methods and properties to inherited interfaces and base classes. This is targeted to C# development and is meant as a supplemental extension to C# Dev Kit. This extension supports pulling public properties and methods to interfaces, and public and protected methods to base classes.", + "version": "1.0.0", "engines": { "vscode": "^1.85.0" }, + "repository": { + "url": "https://github.com/d1820/PullToInterfacor.git", + "type": "git" + }, + "homepage": "https://github.com/d1820/PullToInterfacor/blob/main/README.md", + "icon": "./PullToInterfacor.png", + "galleryBanner": { + "color": "#2596be", + "theme": "light" + }, + "bugs": { + "url": "https://github.com/d1820/PullToInterfacor/issues" + }, "categories": [ "Programming Languages", "Other" @@ -35,7 +49,8 @@ "test-jest": "jest", "test-jest-watch": "jest --watch", "test-jest-coverage": "jest --coverage", - "test:integration": "npm run compile && node ./node_modules/vscode/bin/test" + "test:integration": "npm run compile && node ./node_modules/vscode/bin/test", + "update-toc": "powershell -ExecutionPolicy Unrestricted markdown-toc ./README.md -i --bullets=\"-\" ; Write-Host \"Complete\"" }, "devDependencies": { "@types/jest": "^25.2.3", diff --git a/src/extension.ts b/src/extension.ts index f043bd3..db8d681 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -5,7 +5,7 @@ import * as vscode from 'vscode'; import { getWorkspaceFolder } from './utils/workspace-util'; import * as csharp from './pull-to-interface-csharp'; import { IWindow } from './interfaces/window.interface'; -import { SignatureLineResult, SignatureType, cleanExcessiveNewLines, getLineEnding, getMemberBodyByBrackets, getMemberBodyBySemiColon, getMemberName, getUsingStatements } from './utils/csharp-util'; +import { SignatureLineResult, SignatureType, checkIfAlreadyPulledToInterface, cleanExcessiveNewLines, getLineEnding, getMemberBodyByBrackets, getMemberBodyBySemiColon, getMemberName, getUsingStatements } from './utils/csharp-util'; const extensionName = 'pulltointerfacor.pullto'; @@ -21,7 +21,13 @@ export async function activate(context: vscode.ExtensionContext) { if (editor && editor.document.languageId === 'csharp') { - const subCommands = await csharp.getSubCommandsAsync(workspaceRoot, vscode.window as IWindow); + let subCommands = await csharp.getSubCommandsAsync(workspaceRoot, vscode.window as IWindow); + const signatureResult = csharp.getSignatureToPull(editor, '(public|protected)'); + if (signatureResult?.accessor === 'protected') + { + //filter out Interfaces + subCommands = subCommands.filter(f => !f.startsWith('I')); + } buildSubCommands(subCommands, context); } else @@ -50,9 +56,10 @@ const buildSubCommands = async (subcommands: string[], context: vscode.Extension { const disposable = vscode.commands.registerTextEditorCommand(subCommandName, async (editor) => { - //check if eligible for pull - var signatureResult = csharp.getSignatureToPull(editor, '(public|protected)'); + const signatureResult = csharp.getSignatureToPull(editor, '(public|protected)'); + let methodBodySignature: SignatureLineResult | null = null; + //check if eligible for pull if (!signatureResult?.signature || signatureResult.signatureType === SignatureType.Unknown) { vscode.window.showErrorMessage(`Unsupported pull. Unable to determine what to pull. 'public' properties and 'public' or 'protected' methods are only supported. Please copy manually`); @@ -66,7 +73,7 @@ const buildSubCommands = async (subcommands: string[], context: vscode.Extension vscode.window.showErrorMessage(`More then one file found matching ${subcommand}. Please copy manually`); return; } - + const eol = getLineEnding(editor); const selectedFileDocument = await vscode.workspace.openTextDocument(files[0].path); let selectedFileDocumentContent = selectedFileDocument.getText(); if (!selectedFileDocumentContent) @@ -75,33 +82,28 @@ const buildSubCommands = async (subcommands: string[], context: vscode.Extension return; } - if (selectedFileDocumentContent.indexOf(signatureResult.originalSelectedLine.trim()) > -1) + if (checkIfAlreadyPulledToInterface(selectedFileDocumentContent, signatureResult, eol)) { vscode.window.showWarningMessage(`Member already in ${subcommand}. Skipping pull`); return; } else { - const eol = getLineEnding(editor); - if (!subcommand.startsWith("I")) { - if (signatureResult?.signatureType === SignatureType.Method) + let currentLine = editor.document.lineAt(signatureResult.lineMatchStartsOn).text; + if (currentLine.indexOf("=>") > -1) { - let currentLine = editor.document.lineAt(signatureResult.lineMatchStartsOn).text; - if (currentLine.indexOf("=>") > -1) - { - const body = getMemberBodyBySemiColon(editor, signatureResult); - const methodBodySignature = new SignatureLineResult(body, signatureResult.signatureType, signatureResult.lineMatchStartsOn, signatureResult.accessor); - selectedFileDocumentContent = csharp.addMemberToDocument(subcommand, methodBodySignature, eol, selectedFileDocumentContent, false); + const body = getMemberBodyBySemiColon(editor, signatureResult); + methodBodySignature = new SignatureLineResult(body, signatureResult.signatureType, signatureResult.lineMatchStartsOn, signatureResult.accessor); + selectedFileDocumentContent = csharp.addMemberToDocument(subcommand, methodBodySignature, eol, selectedFileDocumentContent, false); - } - else - { - const body = getMemberBodyByBrackets(editor, signatureResult); - const methodBodySignature = new SignatureLineResult(body, signatureResult.signatureType, signatureResult.lineMatchStartsOn, signatureResult.accessor); - selectedFileDocumentContent = csharp.addMemberToDocument(subcommand, methodBodySignature, eol, selectedFileDocumentContent, false); - } + } + else + { + const body = getMemberBodyByBrackets(editor, signatureResult); + methodBodySignature = new SignatureLineResult(body, signatureResult.signatureType, signatureResult.lineMatchStartsOn, signatureResult.accessor); + selectedFileDocumentContent = csharp.addMemberToDocument(subcommand, methodBodySignature, eol, selectedFileDocumentContent, false); } } else @@ -118,8 +120,8 @@ const buildSubCommands = async (subcommands: string[], context: vscode.Extension { const currentDocumentUsings = getUsingStatements(editor); selectedFileDocumentContent = csharp.addUsingsToDocument(eol, selectedFileDocumentContent, currentDocumentUsings); - selectedFileDocumentContent = cleanExcessiveNewLines(selectedFileDocumentContent); - selectedFileDocumentContent = selectedFileDocumentContent.replace('namespace', `${eol}namespace`); + selectedFileDocumentContent = cleanExcessiveNewLines(selectedFileDocumentContent, eol); + const success = await csharp.applyEditsAsync(files[0].path, selectedFileDocumentContent); if (!success) { @@ -133,6 +135,27 @@ const buildSubCommands = async (subcommands: string[], context: vscode.Extension vscode.window.showWarningMessage(`Unable to save updates to ${subcommand}. Please save file manually`); return; } + if (!subcommand.startsWith("I") && methodBodySignature?.signature) + { + //remove if from current file + const activeFileUrl = editor.document.uri; + const currentFileDocument = await vscode.workspace.openTextDocument(activeFileUrl); + let currentFileDocumentContent = currentFileDocument.getText(); + currentFileDocumentContent = currentFileDocumentContent.replace(methodBodySignature.signature + eol, ''); + currentFileDocumentContent = cleanExcessiveNewLines(currentFileDocumentContent, eol); + const success = await csharp.applyEditsAsync(activeFileUrl.path, currentFileDocumentContent); + if (!success) + { + vscode.window.showErrorMessage(`Unable to remove ${methodBodySignature.signatureType}. Please remove manually`); + return; + } + const wasSaved = await currentFileDocument.save(); + if (!wasSaved) + { + vscode.window.showErrorMessage(`Unable to remove ${methodBodySignature.signatureType}. Please remove manually`); + return; + } + } const memberName = getMemberName(signatureResult!.signature); vscode.window.showInformationMessage(`${memberName} pulled to ${subcommand}`); } @@ -140,27 +163,12 @@ const buildSubCommands = async (subcommands: string[], context: vscode.Extension { vscode.window.showErrorMessage(`Unable to parse file ${subcommand}. Please copy manually`); } - } }); context.subscriptions.push(disposable); } }); - - // const cacheSubCommand = 'Missing File? Clear Cache'; - // const cacheCommand = `${extensionName}.${cacheSubCommand}`; - // const isCacheCommandRegistered = await isSubcommandRegisteredAsync(cacheCommand); - // if (!isCacheCommandRegistered) - // { - // const cacheDisposable = vscode.commands.registerTextEditorCommand(cacheCommand, async (editor) => - // { - // allCommands = null; - // }); - // context.subscriptions.push(cacheDisposable); - // subcommands.push(cacheSubCommand); - // } - // Show a quick pick to execute subcommands const chosenSubcommand = await vscode.window.showQuickPick(subcommands); diff --git a/src/utils/csharp-util.ts b/src/utils/csharp-util.ts index 73601b9..cd07e49 100644 --- a/src/utils/csharp-util.ts +++ b/src/utils/csharp-util.ts @@ -350,9 +350,26 @@ export const getBeginningOfLineIndent = (text: string): number => return 0; }; -export const cleanExcessiveNewLines = (text: string): string => +export const cleanExcessiveNewLines = (text: string, eol: string): string => { - const newlineRegex = /^\s*$/gm; - return text.replace(newlineRegex, ''); + const newlineRegex = /^\s{2,}$/gm; + text = text.replace(newlineRegex, ''); + return text.replace('namespace', `${eol}namespace`); }; + +export const checkIfAlreadyPulledToInterface = (text: string, signatureResult: SignatureLineResult, eol: string): boolean => +{ + const testValue = cleanAllAccessors(signatureResult.originalSelectedLine).trim(); + // if (signatureResult.signatureType === SignatureType.Method) + // { + // return text.indexOf(testValue + eol) > -1; + // } + return text.indexOf(testValue) > -1; +}; + +export const cleanAllAccessors = (text: string): string => +{ + let regex = new RegExp(`((public|private|protected|internal|abstract|virtual|override)[\\s]*)`, 'gm'); + return text.replace(regex,''); +};