diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..c6212cd
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,38 @@
+name: CI
+
+on:
+ push:
+ branches: [ main ]
+ tags: [ 'v*' ]
+ pull_request:
+ branches: [ main ]
+
+jobs:
+ build-and-test:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: '10.0.x'
+
+ - name: Restore dependencies
+ run: dotnet restore SharpLinter.slnx
+
+ - name: Build Solution
+ run: dotnet build SharpLinter.slnx --configuration Release --no-restore
+
+ - name: Run Tests
+ run: dotnet test SharpLinter.slnx --configuration Release --no-build --verbosity normal
+
+ - name: Pack NuGet Packages
+ run: dotnet pack SharpLinter.slnx --configuration Release --no-build -o artifacts
+
+ - name: Upload Artifacts
+ uses: actions/upload-artifact@v4
+ with:
+ name: nuget-packages
+ path: artifacts/*.nupkg
diff --git a/.sharplinter.json b/.sharplinter.json
new file mode 100644
index 0000000..8e5f0e4
--- /dev/null
+++ b/.sharplinter.json
@@ -0,0 +1,16 @@
+{
+ "preset": "recommended",
+ "customRulesFile": ".sharplinter.rules.yaml",
+ "exclude": [
+ "**/bin/**",
+ "**/obj/**",
+ "**/artifacts/**",
+ "**/nupkg/**"
+ ],
+ "formatting": {
+ "enabled": true,
+ "indentSize": 4,
+ "useTabs": false,
+ "newLineForBraces": true
+ }
+}
diff --git a/.sharplinter.rules.yaml b/.sharplinter.rules.yaml
new file mode 100644
index 0000000..f759a28
--- /dev/null
+++ b/.sharplinter.rules.yaml
@@ -0,0 +1,37 @@
+# SharpLinter Custom Rules
+# Define your own rules here without writing C# analyzer code.
+
+rules:
+ # Example 1: Flag TODO or HACK comments in code
+ - id: "CUSTOM001"
+ title: "Clean up temporary comments"
+ description: "HACK, TODO, or FIXME comments should be resolved before committing code."
+ category: "Maintainability"
+ severity: "warning"
+ type: "pattern"
+ pattern:
+ kind: "comment"
+ match: "TODO|HACK|FIXME"
+
+ # Example 2: Limit method length to 40 lines
+ - id: "CUSTOM002"
+ title: "Method exceeds line limit"
+ description: "Methods should not exceed 40 lines. Consider decomposing complex logic."
+ category: "Maintainability"
+ severity: "suggestion"
+ type: "metric"
+ metric:
+ target: "method"
+ measure: "lines"
+ max: 40
+
+ # Example 3: Ban public fields
+ - id: "CUSTOM003"
+ title: "Encapsulation violation"
+ description: "Public instance fields should be refactored to properties."
+ category: "Design"
+ severity: "warning"
+ type: "naming"
+ naming:
+ target: "field"
+ pattern: "^[a-z_][a-zA-Z0-9]*$" # camelCase fields should be private/protected
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..093289f
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2026 rahu619
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/LinterProject.API/Controllers/CodeAnalysisController.cs b/LinterProject.API/Controllers/CodeAnalysisController.cs
deleted file mode 100644
index 305e4fe..0000000
--- a/LinterProject.API/Controllers/CodeAnalysisController.cs
+++ /dev/null
@@ -1,45 +0,0 @@
-using Microsoft.AspNetCore.Mvc;
-using OnlineLinter.Interfaces;
-using System.Net;
-using System.Net.Mime;
-using System.Text;
-
-namespace OnlineLinter.Controllers
-{
- [Route("api/v1/[controller]")]
- [ApiController]
- public class CodeAnalysisController : ControllerBase
- {
- private readonly ICodeAnalysisService _codeAnalysisService;
-
- public CodeAnalysisController(ICodeAnalysisService linterService)
- {
- this._codeAnalysisService = linterService;
- }
-
- ///
- /// Formats and returns a c sharp code snippet
- ///
- ///
- ///
- /// A newly formatted code
- [HttpPost()]
- [Produces(MediaTypeNames.Text.Plain)]
- [ProducesResponseType(StatusCodes.Status200OK)]
- [ProducesResponseType(StatusCodes.Status400BadRequest)]
- [ProducesResponseType(StatusCodes.Status500InternalServerError)]
- public async Task> FormatCode(CancellationToken cancellationToken)
- {
- string? content;
- using (var reader = new StreamReader(Request.Body, Encoding.UTF8))
- {
- content = await reader.ReadToEndAsync();
- }
- ArgumentNullException.ThrowIfNull(content, "Error reading request body");
-
- var formattedCode = await this._codeAnalysisService.GetFormattedCode(content, cancellationToken);
-
- return Ok(formattedCode);
- }
- }
-}
diff --git a/LinterProject.API/ExceptionHandlingMiddleware.cs b/LinterProject.API/ExceptionHandlingMiddleware.cs
deleted file mode 100644
index 12b4996..0000000
--- a/LinterProject.API/ExceptionHandlingMiddleware.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-using System.Net;
-using System.Net.Mime;
-
-namespace OnlineLinter
-{
- public class ExceptionHandlingMiddleware
- {
- private readonly ILogger _logger;
- private readonly RequestDelegate _next;
-
- public ExceptionHandlingMiddleware(ILogger logger,
- RequestDelegate next)
- {
- this._logger = logger;
- this._next = next;
- }
-
- public async Task InvokeAsync(HttpContext httpContext)
- {
- try
- {
- await _next(httpContext);
- }
- catch (Exception ex)
- {
- httpContext.Response.ContentType = MediaTypeNames.Application.Json;
- httpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
-
- if (ex is ArgumentException)
- {
- httpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest;
- }
-
- await httpContext.Response.WriteAsync($"An error occurred while processing your request. Found exception: {ex.Message}");
- }
- }
- }
-}
diff --git a/LinterProject.API/Interfaces/ICodeAnalysisService.cs b/LinterProject.API/Interfaces/ICodeAnalysisService.cs
deleted file mode 100644
index 49bdc7e..0000000
--- a/LinterProject.API/Interfaces/ICodeAnalysisService.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-namespace OnlineLinter.Interfaces
-{
- ///
- /// The interface for the code analyis service.
- ///
- public interface ICodeAnalysisService
- {
- ///
- /// Formats and indents the passed in
- ///
- /// The incoming code
- /// The cancellation token
- ///
- Task GetFormattedCode(string codeSnippet, CancellationToken cancellationToken = default);
- }
-}
diff --git a/LinterProject.API/LinterProject.API.csproj b/LinterProject.API/LinterProject.API.csproj
deleted file mode 100644
index d46dc22..0000000
--- a/LinterProject.API/LinterProject.API.csproj
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
- net6.0
- enable
- enable
-
-
-
-
-
-
-
-
-
-
-
diff --git a/LinterProject.API/Program.cs b/LinterProject.API/Program.cs
deleted file mode 100644
index de7516d..0000000
--- a/LinterProject.API/Program.cs
+++ /dev/null
@@ -1,39 +0,0 @@
-using OnlineLinter;
-using OnlineLinter.Interfaces;
-using OnlineLinter.Services;
-
-var builder = WebApplication.CreateBuilder(args);
-
-// Add services to the container.
-
-builder.Services.AddControllers();
-// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
-builder.Services.AddEndpointsApiExplorer();
-builder.Services.AddSwaggerGen(c =>
-{
- c.SwaggerDoc("v1", new Microsoft.OpenApi.Models.OpenApiInfo { Title = "Linter.API", Version = "v1", Description = "An API to format c# code snippets." });
-});
-
-builder.Services.AddRouting(options => options.LowercaseUrls = true);
-builder.Services.AddSingleton();
-
-var app = builder.Build();
-
-// Configure the HTTP request pipeline.
-if (app.Environment.IsDevelopment())
-{
- app.UseSwagger();
- app.UseSwaggerUI(c =>
- {
- c.SwaggerEndpoint("/swagger/v1/swagger.json", "Linter.API");
- });
-}
-
-//Adding custom middlewares
-app.UseMiddleware();
-
-app.UseHttpsRedirection();
-app.UseAuthorization();
-app.MapControllers();
-
-app.Run();
diff --git a/LinterProject.API/Properties/launchSettings.json b/LinterProject.API/Properties/launchSettings.json
deleted file mode 100644
index f5ea29e..0000000
--- a/LinterProject.API/Properties/launchSettings.json
+++ /dev/null
@@ -1,31 +0,0 @@
-{
- "$schema": "https://json.schemastore.org/launchsettings.json",
- "iisSettings": {
- "windowsAuthentication": false,
- "anonymousAuthentication": true,
- "iisExpress": {
- "applicationUrl": "http://localhost:52916",
- "sslPort": 44302
- }
- },
- "profiles": {
- "OnlineLinter": {
- "commandName": "Project",
- "dotnetRunMessages": true,
- "launchBrowser": true,
- "launchUrl": "swagger",
- "applicationUrl": "https://localhost:7295;http://localhost:5035",
- "environmentVariables": {
- "ASPNETCORE_ENVIRONMENT": "Development"
- }
- },
- "IIS Express": {
- "commandName": "IISExpress",
- "launchBrowser": true,
- "launchUrl": "swagger",
- "environmentVariables": {
- "ASPNETCORE_ENVIRONMENT": "Development"
- }
- }
- }
-}
diff --git a/LinterProject.API/Services/CodeAnalysisService.cs b/LinterProject.API/Services/CodeAnalysisService.cs
deleted file mode 100644
index 7bb1b7a..0000000
--- a/LinterProject.API/Services/CodeAnalysisService.cs
+++ /dev/null
@@ -1,86 +0,0 @@
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CSharp;
-using Microsoft.CodeAnalysis.CSharp.Formatting;
-using Microsoft.CodeAnalysis.Formatting;
-using OnlineLinter.Interfaces;
-using OnlineLinter.SyntaxRewriters;
-
-namespace OnlineLinter.Services
-{
- public class CodeAnalysisService : ICodeAnalysisService
- {
- private readonly ILogger _logger;
-
- public CodeAnalysisService(ILogger logger)
- {
- this._logger = logger;
- }
-
- ///
- public async Task GetFormattedCode(string codeSnippet, CancellationToken cancellationToken = default)
- {
- //TODO: Upgrade to .NET 7
- //https://learn.microsoft.com/en-us/dotnet/api/system.argumentexception.throwifnullorempty?view=net-7.0
-
- if (string.IsNullOrWhiteSpace(codeSnippet))
- {
- throw new ArgumentNullException(nameof(codeSnippet));
- }
-
- if (!ValidateCode(codeSnippet))
- {
- throw new ArgumentException("Error parsing code snippet");
- }
-
- var syntaxTree = CSharpSyntaxTree.ParseText(codeSnippet);
- var root = await syntaxTree.GetRootAsync(cancellationToken);
- var rewriter = new AddBracesRewriter();
- var modifiedRoot = rewriter.Visit(root); //Applying syntax rewriters
-
- var workspace = new AdhocWorkspace();
-
- var options = workspace.Options
- .WithChangedOption(CSharpFormattingOptions.IndentBlock, true)
- .WithChangedOption(CSharpFormattingOptions.IndentBraces, false)
- .WithChangedOption(CSharpFormattingOptions.SpaceAfterColonInBaseTypeDeclaration, true)
- .WithChangedOption(CSharpFormattingOptions.SpaceBeforeColonInBaseTypeDeclaration, false)
- .WithChangedOption(CSharpFormattingOptions.NewLineForCatch, true)
- .WithChangedOption(CSharpFormattingOptions.NewLineForFinally, true)
- .WithChangedOption(CSharpFormattingOptions.NewLineForMembersInObjectInit, true)
- .WithChangedOption(CSharpFormattingOptions.NewLineForMembersInAnonymousTypes, true)
- .WithChangedOption(CSharpFormattingOptions.NewLineForClausesInQuery, true)
- .WithChangedOption(CSharpFormattingOptions.NewLinesForBracesInControlBlocks, true)
- .WithChangedOption(CSharpFormattingOptions.NewLinesForBracesInMethods, true)
- .WithChangedOption(CSharpFormattingOptions.NewLinesForBracesInProperties, true)
- .WithChangedOption(CSharpFormattingOptions.NewLinesForBracesInTypes, true)
- .WithChangedOption(CSharpFormattingOptions.NewLinesForBracesInControlBlocks, true)
- ;
-
-
- var formattedSyntaxNode = Formatter.Format(modifiedRoot, workspace, options, cancellationToken);
-
- return formattedSyntaxNode.ToFullString();
- }
-
- private bool ValidateCode(string codeSnippet)
- {
- var syntaxTree = CSharpSyntaxTree.ParseText(codeSnippet);
- if (syntaxTree is null)
- {
- _logger.LogError("Error parsing syntax : {codeSnippet}", codeSnippet);
- return false;
- }
-
- foreach (var diagnostic in syntaxTree.GetDiagnostics())
- {
- if (diagnostic.Severity == DiagnosticSeverity.Error)
- {
- _logger.LogError("Error parsing syntax : {codeSnippet}", codeSnippet);
- return false;
- }
- }
-
- return true;
- }
- }
-}
diff --git a/LinterProject.API/SyntaxRewriters/AddBracesRewriter.cs b/LinterProject.API/SyntaxRewriters/AddBracesRewriter.cs
deleted file mode 100644
index a61c665..0000000
--- a/LinterProject.API/SyntaxRewriters/AddBracesRewriter.cs
+++ /dev/null
@@ -1,52 +0,0 @@
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CSharp;
-using Microsoft.CodeAnalysis.CSharp.Syntax;
-
-namespace OnlineLinter.SyntaxRewriters
-{
- public class AddBracesRewriter : CSharpSyntaxRewriter
- {
- public override SyntaxNode VisitIfStatement(IfStatementSyntax node)
- {
- var newIfStatement = node.WithStatement(AddBracesToStatement(node.Statement));
-
- if (newIfStatement.Else != null)
- {
- var newElseClause = newIfStatement.Else.WithStatement(AddBracesToStatement(newIfStatement.Else.Statement));
- newIfStatement = newIfStatement.WithElse(newElseClause);
- }
-
- return base.VisitIfStatement(newIfStatement);
- }
-
- public override SyntaxNode VisitWhileStatement(WhileStatementSyntax node)
- {
- return node.WithStatement(AddBracesToStatement(node.Statement));
- }
-
- public override SyntaxNode VisitForStatement(ForStatementSyntax node)
- {
- return node.WithStatement(AddBracesToStatement(node.Statement));
- }
-
- public override SyntaxNode VisitForEachStatement(ForEachStatementSyntax node)
- {
- return node.WithStatement(AddBracesToStatement(node.Statement));
- }
-
- public override SyntaxNode VisitDoStatement(DoStatementSyntax node)
- {
- return node.WithStatement(AddBracesToStatement(node.Statement));
- }
-
- private StatementSyntax AddBracesToStatement(StatementSyntax statement)
- {
- if (statement is BlockSyntax)
- {
- return statement;
- }
-
- return SyntaxFactory.Block(statement);
- }
- }
-}
diff --git a/LinterProject.API/appsettings.Development.json b/LinterProject.API/appsettings.Development.json
deleted file mode 100644
index 0c208ae..0000000
--- a/LinterProject.API/appsettings.Development.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "Logging": {
- "LogLevel": {
- "Default": "Information",
- "Microsoft.AspNetCore": "Warning"
- }
- }
-}
diff --git a/LinterProject.API/appsettings.json b/LinterProject.API/appsettings.json
deleted file mode 100644
index 10f68b8..0000000
--- a/LinterProject.API/appsettings.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "Logging": {
- "LogLevel": {
- "Default": "Information",
- "Microsoft.AspNetCore": "Warning"
- }
- },
- "AllowedHosts": "*"
-}
diff --git a/LinterProject.Tests/CodeAnalysisServiceTests.cs b/LinterProject.Tests/CodeAnalysisServiceTests.cs
deleted file mode 100644
index e1815ba..0000000
--- a/LinterProject.Tests/CodeAnalysisServiceTests.cs
+++ /dev/null
@@ -1,47 +0,0 @@
-using FluentAssertions;
-using Microsoft.Extensions.Logging.Abstractions;
-using NUnit.Framework;
-using OnlineLinter.Interfaces;
-using OnlineLinter.Services;
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Threading.Tasks;
-
-namespace OnlineLinter.Test
-{
- [TestFixture]
- public class CodeAnalysisServiceTests
- {
- [TestCaseSource(nameof(CodeSnippetTestData))]
- public async Task ShouldFormatValidCode(string expectedCode, string inputCode)
- {
- //Arrange
- //Act
- string actualCode = await new CodeAnalysisService(NullLogger.Instance).GetFormattedCode(inputCode);
-
- //Assert
- actualCode.Should().NotBeEmpty().And.BeEquivalentTo(expectedCode);
- }
-
- private static IEnumerable