diff --git a/.idea/.idea.EmmyLuaAnalyzer/.idea/.name b/.idea/.idea.EmmyLuaAnalyzer/.idea/.name new file mode 100644 index 00000000..9fa2b134 --- /dev/null +++ b/.idea/.idea.EmmyLuaAnalyzer/.idea/.name @@ -0,0 +1 @@ +EmmyLuaAnalyzer \ No newline at end of file diff --git a/EmmyLua/CodeAnalysis/Compilation/Analyzer/DeclarationAnalyzer/DeclarationBuilder.cs b/EmmyLua/CodeAnalysis/Compilation/Analyzer/DeclarationAnalyzer/DeclarationBuilder.cs index 35748d3a..194b0897 100644 --- a/EmmyLua/CodeAnalysis/Compilation/Analyzer/DeclarationAnalyzer/DeclarationBuilder.cs +++ b/EmmyLua/CodeAnalysis/Compilation/Analyzer/DeclarationAnalyzer/DeclarationBuilder.cs @@ -238,6 +238,11 @@ public void WalkIn(LuaSyntaxElement node) Compilation.Diagnostics.AddMeta(DocumentId); break; } + case LuaDocTagDiagnosticSyntax diagnosticSyntax: + { + AnalyzeDiagnostic(diagnosticSyntax); + break; + } } } @@ -1099,4 +1104,57 @@ private void IndexDocNameType(LuaDocNameTypeSyntax docNameType) { ProjectIndex.AddNameType(DocumentId, docNameType); } + + private void AnalyzeDiagnostic(LuaDocTagDiagnosticSyntax diagnosticSyntax) + { + if (diagnosticSyntax is + { + Action: { RepresentText: { } actionName }, + Diagnostics: { DiagnosticNames: { } diagnosticNames } + }) + { + switch (actionName) + { + case "disable-next-line": + { + if (diagnosticSyntax.Parent is LuaCommentSyntax { Owner.Range: { } range }) + { + foreach (var diagnosticName in diagnosticNames) + { + if (diagnosticName is { RepresentText: { } name }) + { + Compilation.Diagnostics.AddDiagnosticDisableNextLine(DocumentId, range, name); + } + } + } + + break; + } + case "disable": + { + foreach (var diagnosticName in diagnosticNames) + { + if (diagnosticName is { RepresentText: { } name }) + { + Compilation.Diagnostics.AddDiagnosticDisable(DocumentId, name); + } + } + + break; + } + case "enable": + { + foreach (var diagnosticName in diagnosticNames) + { + if (diagnosticName is { RepresentText: { } name }) + { + Compilation.Diagnostics.AddDiagnosticEnable(DocumentId, name); + } + } + + break; + } + } + } + } } diff --git a/EmmyLua/CodeAnalysis/Compilation/Infer/TypeInfer.cs b/EmmyLua/CodeAnalysis/Compilation/Infer/TypeInfer.cs index dddf0efb..3d1fd5d5 100644 --- a/EmmyLua/CodeAnalysis/Compilation/Infer/TypeInfer.cs +++ b/EmmyLua/CodeAnalysis/Compilation/Infer/TypeInfer.cs @@ -74,10 +74,15 @@ public static LuaType InferFuncType(LuaDocFuncTypeSyntax funcType, SearchContext var typedParameters = new List(); foreach (var typedParam in funcType.ParamList) { - if (typedParam is { Name: { } name }) + if (typedParam is { Name: { } name, Nullable: {} nullable }) { + var type = context.Infer(typedParam.Type); + if (nullable) + { + type = type.Union(Builtin.Nil); + } var paramDeclaration = new ParameterLuaDeclaration( - name.RepresentText, new(typedParam), context.Infer(typedParam.Type)); + name.RepresentText, new(typedParam), type); typedParameters.Add(paramDeclaration); } else if (typedParam is { VarArgs: { } varArgs }) diff --git a/EmmyLua/CodeAnalysis/Compile/Grammar/Doc/Tags.cs b/EmmyLua/CodeAnalysis/Compile/Grammar/Doc/Tags.cs index 37cc9c38..dece6c8d 100644 --- a/EmmyLua/CodeAnalysis/Compile/Grammar/Doc/Tags.cs +++ b/EmmyLua/CodeAnalysis/Compile/Grammar/Doc/Tags.cs @@ -559,12 +559,7 @@ private static CompleteMarker TagDiagnostic(LuaDocParser p) if (p.Current is LuaTokenKind.TkColon) { p.Bump(); - p.Expect(LuaTokenKind.TkName); - while (p.Current is LuaTokenKind.TkComma) - { - p.Bump(); - p.Expect(LuaTokenKind.TkName); - } + DiagnosticList(p); } return m.Complete(p, LuaSyntaxKind.DocDiagnostic); @@ -575,6 +570,25 @@ private static CompleteMarker TagDiagnostic(LuaDocParser p) } } + private static CompleteMarker DiagnosticList(LuaDocParser p) + { + var m = p.Marker(); + try + { + p.Expect(LuaTokenKind.TkName); + while (p.Current is LuaTokenKind.TkComma) + { + p.Bump(); + p.Expect(LuaTokenKind.TkName); + } + return m.Complete(p, LuaSyntaxKind.DiagnosticNameList); + } + catch (UnexpectedTokenException e) + { + return m.Fail(p, LuaSyntaxKind.DiagnosticNameList, e.Message); + } + } + private static CompleteMarker TagVersion(LuaDocParser p) { p.SetState(LuaDocLexerState.Normal); diff --git a/EmmyLua/CodeAnalysis/Compile/Parser/Marker.cs b/EmmyLua/CodeAnalysis/Compile/Parser/Marker.cs index d68f569f..e51e5d9b 100644 --- a/EmmyLua/CodeAnalysis/Compile/Parser/Marker.cs +++ b/EmmyLua/CodeAnalysis/Compile/Parser/Marker.cs @@ -21,9 +21,9 @@ public interface IMarkerEventContainer public Marker Marker(); } -public struct Marker(int position) +public readonly struct Marker(int position) { - public int Position { get; set; } = position; + public int Position { get; } = position; public CompleteMarker Complete(IMarkerEventContainer p, LuaSyntaxKind kind) { diff --git a/EmmyLua/CodeAnalysis/Diagnostics/DiagnosticConfig.cs b/EmmyLua/CodeAnalysis/Diagnostics/DiagnosticConfig.cs index e8200a48..39cbbbc9 100644 --- a/EmmyLua/CodeAnalysis/Diagnostics/DiagnosticConfig.cs +++ b/EmmyLua/CodeAnalysis/Diagnostics/DiagnosticConfig.cs @@ -1,6 +1,15 @@ -namespace EmmyLua.CodeAnalysis.Diagnostics; +using EmmyLua.CodeAnalysis.Document; + +namespace EmmyLua.CodeAnalysis.Diagnostics; public class DiagnosticConfig { public HashSet Globals { get; } = new(); + + public HashSet WorkspaceDisabledCodes { get; } = new(); +} + +public class DisableNextLine +{ + public Dictionary> Ranges { get; } = new(); } diff --git a/EmmyLua/CodeAnalysis/Diagnostics/DiagnosticContext.cs b/EmmyLua/CodeAnalysis/Diagnostics/DiagnosticContext.cs index 4125b49e..4a1502fa 100644 --- a/EmmyLua/CodeAnalysis/Diagnostics/DiagnosticContext.cs +++ b/EmmyLua/CodeAnalysis/Diagnostics/DiagnosticContext.cs @@ -2,14 +2,19 @@ namespace EmmyLua.CodeAnalysis.Diagnostics; -public class DiagnosticContext(LuaDocument document, DiagnosticConfig config) +public class DiagnosticContext(LuaDocument document, LuaDiagnostics luaDiagnostics) { + private LuaDiagnostics LuaDiagnostics { get; } = luaDiagnostics; + public LuaDocument Document { get; } = document; - public DiagnosticConfig Config { get; } = config; + public DiagnosticConfig Config => LuaDiagnostics.Config; public void Report(Diagnostic diagnostic) { - Document.SyntaxTree.PushDiagnostic(diagnostic); + if (luaDiagnostics.CanAddDiagnostic(Document.Id, diagnostic.Code, diagnostic.Range)) + { + Document.SyntaxTree.PushDiagnostic(diagnostic); + } } } diff --git a/EmmyLua/CodeAnalysis/Diagnostics/Handlers/DiagnosticHandlerBase.cs b/EmmyLua/CodeAnalysis/Diagnostics/Handlers/DiagnosticHandlerBase.cs index 36d6bd81..0c42be46 100644 --- a/EmmyLua/CodeAnalysis/Diagnostics/Handlers/DiagnosticHandlerBase.cs +++ b/EmmyLua/CodeAnalysis/Diagnostics/Handlers/DiagnosticHandlerBase.cs @@ -2,10 +2,12 @@ namespace EmmyLua.CodeAnalysis.Diagnostics.Handlers; -public abstract class DiagnosticHandlerBase(LuaCompilation compilation) +public abstract class DiagnosticHandlerBase(LuaCompilation compilation, List codes) { public LuaCompilation Compilation { get; } = compilation; + public List Codes { get; } = codes; + public virtual void Check(DiagnosticContext context) { } diff --git a/EmmyLua/CodeAnalysis/Diagnostics/Handlers/TypeCheckHandler.cs b/EmmyLua/CodeAnalysis/Diagnostics/Handlers/TypeCheckHandler.cs index f2064737..9263dc36 100644 --- a/EmmyLua/CodeAnalysis/Diagnostics/Handlers/TypeCheckHandler.cs +++ b/EmmyLua/CodeAnalysis/Diagnostics/Handlers/TypeCheckHandler.cs @@ -2,7 +2,7 @@ namespace EmmyLua.CodeAnalysis.Diagnostics.Handlers; -public class TypeCheckHandler(LuaCompilation compilation) : DiagnosticHandlerBase(compilation) +public class TypeCheckHandler(LuaCompilation compilation) : DiagnosticHandlerBase(compilation, []) { public override void Check(DiagnosticContext context) { diff --git a/EmmyLua/CodeAnalysis/Diagnostics/Handlers/UndefinedGlobalHandler.cs b/EmmyLua/CodeAnalysis/Diagnostics/Handlers/UndefinedGlobalHandler.cs index 8dc55f35..17354532 100644 --- a/EmmyLua/CodeAnalysis/Diagnostics/Handlers/UndefinedGlobalHandler.cs +++ b/EmmyLua/CodeAnalysis/Diagnostics/Handlers/UndefinedGlobalHandler.cs @@ -4,7 +4,9 @@ namespace EmmyLua.CodeAnalysis.Diagnostics.Handlers; -public class UndefinedGlobalHandler(LuaCompilation compilation) : DiagnosticHandlerBase(compilation) +public class UndefinedGlobalHandler(LuaCompilation compilation) + : DiagnosticHandlerBase(compilation, + [DiagnosticCode.UndefinedGlobal, DiagnosticCode.NeedImport]) { public override void Check(DiagnosticContext context) { @@ -57,6 +59,7 @@ public override void Check(DiagnosticContext context) )); continue; } + if (documentIds.Count == 1) { var moduleIndex = Compilation.Workspace.ModuleGraph.GetModuleInfo(documentIds.First()); diff --git a/EmmyLua/CodeAnalysis/Diagnostics/Handlers/UnusedHandler.cs b/EmmyLua/CodeAnalysis/Diagnostics/Handlers/UnusedHandler.cs index 32543175..2cd96aaa 100644 --- a/EmmyLua/CodeAnalysis/Diagnostics/Handlers/UnusedHandler.cs +++ b/EmmyLua/CodeAnalysis/Diagnostics/Handlers/UnusedHandler.cs @@ -5,10 +5,8 @@ namespace EmmyLua.CodeAnalysis.Diagnostics.Handlers; -public class UnusedHandler(LuaCompilation compilation) : DiagnosticHandlerBase(compilation) +public class UnusedHandler(LuaCompilation compilation) : DiagnosticHandlerBase(compilation, [DiagnosticCode.Unused]) { - private DiagnosticCode Code { get; } = DiagnosticCode.Unused; - public override void Check(DiagnosticContext context) { var semanticModel = Compilation.GetSemanticModel(context.Document.Id); @@ -59,7 +57,7 @@ private void LocalOrParamUnusedCheck( { context.Report(new Diagnostic( DiagnosticSeverity.Hint, - Code, + DiagnosticCode.Unused, "unused variable", luaDeclaration.Ptr.Range, DiagnosticTag.Unnecessary diff --git a/EmmyLua/CodeAnalysis/Diagnostics/LuaDiagnostics.cs b/EmmyLua/CodeAnalysis/Diagnostics/LuaDiagnostics.cs index 45c795fa..5e59dd21 100644 --- a/EmmyLua/CodeAnalysis/Diagnostics/LuaDiagnostics.cs +++ b/EmmyLua/CodeAnalysis/Diagnostics/LuaDiagnostics.cs @@ -17,6 +17,12 @@ public class LuaDiagnostics(LuaCompilation compilation) private HashSet IsMetaDocument { get; } = new(); + private Dictionary> Disables { get; } = new(); + + private Dictionary> Enables { get; } = new(); + + private Dictionary DisableNextLines { get; } = new(); + public DiagnosticConfig Config { get; set; } = new(); public void Check(LuaDocument document) @@ -26,11 +32,60 @@ public void Check(LuaDocument document) return; } - var context = new DiagnosticContext(document, Config); + var context = new DiagnosticContext(document, this); foreach (var handler in Handlers) { - handler.Check(context); + if (CanCheck(document.Id, handler)) + { + handler.Check(context); + } + } + } + + public bool CanCheck(LuaDocumentId documentId, DiagnosticHandlerBase handlerBase) + { + var codes = handlerBase.Codes; + return codes.Count != 0 && codes.Any(code => CanCheckCode(documentId, code)); + } + + private bool CanCheckCode(LuaDocumentId documentId, DiagnosticCode code) + { + var shouldCheck = !Config.WorkspaceDisabledCodes.Contains(code); + if (Disables.TryGetValue(documentId, out var disables)) + { + if (disables.Contains(code)) + { + shouldCheck = false; + } + } + + if (Enables.TryGetValue(documentId, out var enables)) + { + if (enables.Contains(code)) + { + shouldCheck = true; + } + } + + return shouldCheck; + } + + public bool CanAddDiagnostic(LuaDocumentId documentId, DiagnosticCode code, SourceRange range) + { + if (!CanCheckCode(documentId, code)) + { + return false; + } + + if (DisableNextLines.TryGetValue(documentId, out var disableNextLine)) + { + if (disableNextLine.Ranges.TryGetValue(code, out var ranges)) + { + return ranges.All(disableRange => !disableRange.Intersect(range)); + } } + + return true; } public void AddMeta(LuaDocumentId documentId) @@ -38,8 +93,67 @@ public void AddMeta(LuaDocumentId documentId) IsMetaDocument.Add(documentId); } + public void AddDiagnosticDisable(LuaDocumentId documentId, string diagnosticName) + { + var code = DiagnosticCodeHelper.GetCode(diagnosticName); + if (code != DiagnosticCode.None) + { + if (!Disables.TryGetValue(documentId, out var disables)) + { + disables = new HashSet(); + Disables[documentId] = disables; + } + + disables.Add(code); + } + } + + public void AddDiagnosticEnable(LuaDocumentId documentId, string diagnosticName) + { + var code = DiagnosticCodeHelper.GetCode(diagnosticName); + if (code != DiagnosticCode.None) + { + if (!Enables.TryGetValue(documentId, out var enables)) + { + enables = new HashSet(); + Enables[documentId] = enables; + } + + enables.Add(code); + } + } + + public void AddDiagnosticDisableNextLine(LuaDocumentId documentId, SourceRange range, string diagnosticName) + { + var code = DiagnosticCodeHelper.GetCode(diagnosticName); + if (code != DiagnosticCode.None) + { + if (!DisableNextLines.TryGetValue(documentId, out var disableNextLine)) + { + disableNextLine = new DisableNextLine(); + DisableNextLines[documentId] = disableNextLine; + } + + if (!disableNextLine.Ranges.TryGetValue(code, out var ranges)) + { + ranges = new List(); + disableNextLine.Ranges[code] = ranges; + } + + ranges.Add(range); + } + } + public void RemoveCache(LuaDocumentId documentId) { IsMetaDocument.Remove(documentId); + Disables.Remove(documentId); + Enables.Remove(documentId); + DisableNextLines.Remove(documentId); + } + + public List GetDiagnosticNames() + { + return Handlers.SelectMany(handler => handler.Codes.Select(DiagnosticCodeHelper.GetName)).ToList(); } } diff --git a/EmmyLua/CodeAnalysis/Kind/LuaSyntaxKind.cs b/EmmyLua/CodeAnalysis/Kind/LuaSyntaxKind.cs index 06759784..b687d847 100644 --- a/EmmyLua/CodeAnalysis/Kind/LuaSyntaxKind.cs +++ b/EmmyLua/CodeAnalysis/Kind/LuaSyntaxKind.cs @@ -94,7 +94,7 @@ public enum LuaSyntaxKind : ushort TypedParameter, GenericParameter, GenericDeclareList, - + DiagnosticNameList, // start with '#' or '@' Description } diff --git a/EmmyLua/CodeAnalysis/Syntax/Node/SyntaxFactory.cs b/EmmyLua/CodeAnalysis/Syntax/Node/SyntaxFactory.cs index 7c1d3dcb..c24bcfd2 100644 --- a/EmmyLua/CodeAnalysis/Syntax/Node/SyntaxFactory.cs +++ b/EmmyLua/CodeAnalysis/Syntax/Node/SyntaxFactory.cs @@ -94,6 +94,7 @@ public static LuaSyntaxElement CreateSyntax(GreenNode greenNode, LuaSyntaxTree t LuaSyntaxKind.GenericDeclareList => new LuaDocGenericDeclareListSyntax(greenNode, tree, parent, startOffset), LuaSyntaxKind.Description => new LuaDescriptionSyntax(greenNode, tree, parent, startOffset), LuaSyntaxKind.TypeGenericVararg => new LuaDocGenericVarargTypeSyntax(greenNode, tree, parent, startOffset), + LuaSyntaxKind.DiagnosticNameList => new LuaDocDiagnosticNameListSyntax(greenNode, tree, parent, startOffset), _ => throw new ArgumentException("Unexpected SyntaxKind") }; } diff --git a/EmmyLua/CodeAnalysis/Syntax/Node/SyntaxNodes/DocTag.cs b/EmmyLua/CodeAnalysis/Syntax/Node/SyntaxNodes/DocTag.cs index 7074c027..179f8560 100644 --- a/EmmyLua/CodeAnalysis/Syntax/Node/SyntaxNodes/DocTag.cs +++ b/EmmyLua/CodeAnalysis/Syntax/Node/SyntaxNodes/DocTag.cs @@ -165,9 +165,15 @@ public class LuaDocTagVersionSyntax(GreenNode greenNode, LuaSyntaxTree tree, Lua public class LuaDocTagDiagnosticSyntax(GreenNode greenNode, LuaSyntaxTree tree, LuaSyntaxElement? parent, int startOffset) : LuaDocTagSyntax(greenNode, tree, parent, startOffset) { - public LuaNameToken? State => FirstChild(); + public LuaNameToken? Action => FirstChild(); - public IEnumerable Diagnostics => ChildNodes().Skip(1); + public LuaDocDiagnosticNameListSyntax? Diagnostics => FirstChild(); +} + +public class LuaDocDiagnosticNameListSyntax(GreenNode greenNode, LuaSyntaxTree tree, LuaSyntaxElement? parent, int startOffset) + : LuaSyntaxNode(greenNode, tree, parent, startOffset) +{ + public IEnumerable DiagnosticNames => ChildNodes(); } public class LuaDocTagOperatorSyntax(GreenNode greenNode, LuaSyntaxTree tree, LuaSyntaxElement? parent, int startOffset) diff --git a/EmmyLua/CodeAnalysis/Syntax/Node/SyntaxNodes/DocTypes.cs b/EmmyLua/CodeAnalysis/Syntax/Node/SyntaxNodes/DocTypes.cs index 0bbb0623..cc4f77c2 100644 --- a/EmmyLua/CodeAnalysis/Syntax/Node/SyntaxNodes/DocTypes.cs +++ b/EmmyLua/CodeAnalysis/Syntax/Node/SyntaxNodes/DocTypes.cs @@ -49,6 +49,8 @@ public class LuaDocTypedParamSyntax(GreenNode greenNode, LuaSyntaxTree tree, Lua public LuaDocTypeSyntax? Type => FirstChild(); + public bool Nullable => FirstChildToken(LuaTokenKind.TkNullable) != null; + public bool IsVarArgs => VarArgs != null; } diff --git a/LanguageServer/CodeAction/CodeActionBuilder.cs b/LanguageServer/CodeAction/CodeActionBuilder.cs index 79f71521..e096d1c4 100644 --- a/LanguageServer/CodeAction/CodeActionBuilder.cs +++ b/LanguageServer/CodeAction/CodeActionBuilder.cs @@ -1,9 +1,13 @@ using EmmyLua.CodeAnalysis.Diagnostics; +using EmmyLua.CodeAnalysis.Document; using LanguageServer.CodeAction.CodeActions; +using LanguageServer.ExecuteCommand.Commands; using LanguageServer.Server; using OmniSharp.Extensions.LanguageServer.Protocol.Models; using Diagnostic = OmniSharp.Extensions.LanguageServer.Protocol.Models.Diagnostic; using DiagnosticCode = EmmyLua.CodeAnalysis.Diagnostics.DiagnosticCode; +using Range = OmniSharp.Extensions.LanguageServer.Protocol.Models.Range; + namespace LanguageServer.CodeAction; @@ -45,9 +49,50 @@ public List Build(IEnumerable diagnostics, stri result.AddRange(codeAction.GetCodeActions(data, currentDocumentId.Value, context) .Select(CommandOrCodeAction.From)); } + + AddDisableActions(result, codeString, currentDocumentId.Value, diagnostic.Range); } } return result; } + + private void AddDisableActions(List result, string codeString, LuaDocumentId documentId, + Range range) + { + if (codeString == "syntax-error") + { + return; + } + + result.Add(CommandOrCodeAction.From( + DiagnosticAction.MakeCommand( + $"Disable current line diagnostic ({codeString})", + codeString, + "disable-next-line", + documentId, + range + ) + )); + + result.Add(CommandOrCodeAction.From( + DiagnosticAction.MakeCommand( + $"Disable current file diagnostic ({codeString})", + codeString, + "disable", + documentId, + range + ) + )); + + // result.Add(CommandOrCodeAction.From( + // DiagnosticAction.MakeCommand( + // $"Disable workspace diagnostic ({codeString})", + // codeString, + // "disable-workspace", + // documentId, + // range + // ) + // )); + } } \ No newline at end of file diff --git a/LanguageServer/Completion/CompleteProvider/DocProvider.cs b/LanguageServer/Completion/CompleteProvider/DocProvider.cs index 3ee28d7a..c6047c2c 100644 --- a/LanguageServer/Completion/CompleteProvider/DocProvider.cs +++ b/LanguageServer/Completion/CompleteProvider/DocProvider.cs @@ -13,6 +13,8 @@ public class DocProvider : ICompleteProviderBase "type", "overload", "generic", "async", "cast", "private", "protected", "public", "operator", "meta", "version", "as", "nodiscard", "diagnostic", // "package", ]; + + private List Actions { get; } = ["disable-next-line", "disable", "enable"]; public void AddCompletion(CompleteContext context) { @@ -34,6 +36,16 @@ public void AddCompletion(CompleteContext context) AddTypeNameCompletion(context); break; } + case LuaNameToken { Parent: LuaDocTagDiagnosticSyntax }: + { + AddDiagnosticActionCompletion(context); + break; + } + case LuaNameToken { Parent: LuaDocDiagnosticNameListSyntax }: + { + AddDiagnosticCodeCompletion(context); + break; + } } } @@ -103,4 +115,31 @@ private static CompletionItemKind ConvertTypedName(NamedTypeKind kind) _ => CompletionItemKind.Text, }; } + + private void AddDiagnosticActionCompletion(CompleteContext context) + { + foreach (var action in Actions) + { + context.Add(new CompletionItem + { + Label = action, + Kind = CompletionItemKind.EnumMember, + Detail = "action", + }); + } + } + + private void AddDiagnosticCodeCompletion(CompleteContext context) + { + var diagnosticCodes = context.SemanticModel.Compilation.Diagnostics.GetDiagnosticNames(); + foreach (var diagnosticCode in diagnosticCodes) + { + context.Add(new CompletionItem + { + Label = diagnosticCode, + Kind = CompletionItemKind.EnumMember, + Detail = "diagnostic code", + }); + } + } } \ No newline at end of file diff --git a/LanguageServer/Configuration/LuaConfig.cs b/LanguageServer/Configuration/LuaConfig.cs index 4a1edef0..1f2cb8dd 100644 --- a/LanguageServer/Configuration/LuaConfig.cs +++ b/LanguageServer/Configuration/LuaConfig.cs @@ -137,7 +137,7 @@ public LuaFeatures GetFeatures() { var features = new LuaFeatures(); var rc = DotLuaRc; - if (rc.Workspace?.IgnoreDirs is { } ignoreDirs) + if (rc.Workspace.IgnoreDirs is { } ignoreDirs) { features.ExcludeFolders = ignoreDirs; } @@ -147,7 +147,7 @@ public LuaFeatures GetFeatures() features.DontIndexMaxFileSize = preloadFileSize; } - if (rc.Runtime?.Version is { } version) + if (rc.Runtime.Version is { } version) { switch (version) { diff --git a/LanguageServer/ExecuteCommand/CommandExecutor.cs b/LanguageServer/ExecuteCommand/CommandExecutor.cs index 1764c9e9..f0248dfc 100644 --- a/LanguageServer/ExecuteCommand/CommandExecutor.cs +++ b/LanguageServer/ExecuteCommand/CommandExecutor.cs @@ -20,7 +20,8 @@ public class CommandExecutor( private List Commands { get; } = [ - new AutoRequire() + new AutoRequire(), + new DiagnosticAction() ]; public List GetCommands() diff --git a/LanguageServer/ExecuteCommand/Commands/DiagnosticAction.cs b/LanguageServer/ExecuteCommand/Commands/DiagnosticAction.cs new file mode 100644 index 00000000..04fcd1ad --- /dev/null +++ b/LanguageServer/ExecuteCommand/Commands/DiagnosticAction.cs @@ -0,0 +1,163 @@ +using EmmyLua.CodeAnalysis.Document; +using EmmyLua.CodeAnalysis.Kind; +using EmmyLua.CodeAnalysis.Syntax.Node.SyntaxNodes; +using MediatR; +using Newtonsoft.Json.Linq; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using Range = OmniSharp.Extensions.LanguageServer.Protocol.Models.Range; + +namespace LanguageServer.ExecuteCommand.Commands; + +public class DiagnosticAction : ICommandBase +{ + private static readonly string CommandName = "emmy.diagnosticAction"; + + public string Name { get; } = CommandName; + + public async Task ExecuteAsync(JArray? parameters, CommandExecutor executor) + { + if (parameters is null or { Count: < 3 }) + { + return await Unit.Task; + } + + var codeName = parameters.Value(0); + var action = parameters.Value(1); + var documentId = LuaDocumentId.VirtualDocumentId; + if (parameters.Value(2) is { } id && int.TryParse(id, out var idInt)) + { + documentId = new LuaDocumentId(idInt); + } + + var document = executor.Context.LuaWorkspace.GetDocument(documentId); + if (document is null) + { + return await Unit.Task; + } + + var offset = 0; + if (parameters.Value(3) is { } pos) + { + var parts = pos.Split('_').Select(int.Parse).ToList(); + offset = document.GetOffset(parts[0], parts[1]); + } + + switch (action) + { + case "disable-next-line": + { + var token = document.SyntaxTree.SyntaxRoot.TokenAt(offset); + var stat = token?.Ancestors.OfType().FirstOrDefault(); + if (stat is not null && codeName is not null) + { + await ApplyStatDisableAsync(document, stat, codeName, executor); + } + + break; + } + case "disable": + { + if (codeName is not null) + { + await ApplyDisableFileAsync(document, codeName, executor); + } + + break; + } + } + + return await Unit.Task; + } + + private async Task ApplyStatDisableAsync(LuaDocument document, LuaStatSyntax stat, string codeName, + CommandExecutor executor) + { + var tagDiagnostic = stat.Comments + .FirstOrDefault()? + .DocList.OfType() + .FirstOrDefault(); + if (tagDiagnostic is { Diagnostics.Range: { } prevRange, Action.RepresentText: "disable-next-line" }) + { + var line = document.GetLine(prevRange.EndOffset); + var col = document.GetCol(prevRange.EndOffset); + await executor.ApplyEditAsync(document.Uri, new TextEdit() + { + NewText = $", {codeName}", + Range = new Range() + { + Start = new Position() { Line = line, Character = col }, + End = new Position() { Line = line, Character = col } + } + }); + } + else + { + var indentText = string.Empty; + if (stat.GetPrevToken() is LuaWhitespaceToken { Kind: LuaTokenKind.TkWhitespace, RepresentText: {} indentText2 }) + { + indentText = indentText2; + } + + var line = document.GetLine(stat.Range.StartOffset); + var col = document.GetCol(stat.Range.StartOffset); + await executor.ApplyEditAsync(document.Uri, new TextEdit() + { + NewText = $"---@diagnostic disable-next-line: {codeName}\n{indentText}", + Range = new Range() + { + Start = new Position() { Line = line, Character = col }, + End = new Position() { Line = line, Character = col } + } + }); + } + } + + private async Task ApplyDisableFileAsync(LuaDocument document, string codeName, CommandExecutor executor) + { + var firstChild = document.SyntaxTree.SyntaxRoot.Block?.ChildrenWithTokens.FirstOrDefault(); + if (firstChild is LuaCommentSyntax commentSyntax) + { + var tagDiagnostic = commentSyntax.DocList.OfType().FirstOrDefault(); + if (tagDiagnostic is not null) + { + if (tagDiagnostic is { Diagnostics.Range: { } prevRange, Action.RepresentText: "disable" }) + { + var line = document.GetLine(prevRange.EndOffset); + var col = document.GetCol(prevRange.EndOffset); + await executor.ApplyEditAsync(document.Uri, new TextEdit() + { + NewText = $", {codeName}", + Range = new Range() + { + Start = new Position() { Line = line, Character = col }, + End = new Position() { Line = line, Character = col } + } + }); + return; + } + } + } + + await executor.ApplyEditAsync(document.Uri, new TextEdit() + { + NewText = $"---@diagnostic disable: {codeName}\n", + Range = new Range() + { + Start = new Position() { Line = 0, Character = 0 }, + End = new Position() { Line = 0, Character = 0 } + } + }); + + } + + public static Command MakeCommand(string title, string codeName, string action, LuaDocumentId documentId, + Range range) + { + return new Command() + { + Title = title, + Name = CommandName, + Arguments = [codeName, action, documentId.Id, $"{range.Start.Line}_{range.Start.Character}"] + }; + } +} \ No newline at end of file diff --git a/LanguageServer/InlayHint/InlayHintBuilder.cs b/LanguageServer/InlayHint/InlayHintBuilder.cs index 575e777e..c0305080 100644 --- a/LanguageServer/InlayHint/InlayHintBuilder.cs +++ b/LanguageServer/InlayHint/InlayHintBuilder.cs @@ -164,7 +164,7 @@ private void CallExprHint(SemanticModel semanticModel, List hints hints.Add(new InlayHintType() { Position = callExpr.Range.StartOffset.ToLspPosition(semanticModel.Document), - Label = new StringOrInlayHintLabelParts("await:"), + Label = new StringOrInlayHintLabelParts("await "), Kind = InlayHintKind.Type, PaddingRight = true });