├── .gitattributes ├── .gitignore ├── NuGet.Config ├── Peachpie.LanguageServer.sln ├── README.md ├── samples └── diagnostics │ ├── .vscode │ ├── launch.json │ └── tasks.json │ ├── project.json │ ├── undefined-types-members.php │ └── undefined-variables.php └── src ├── Peachpie.LanguageServer ├── .vscode │ ├── launch.json │ └── tasks.json ├── CompilationDiagnosticBroker.cs ├── EnvironmentUtils.cs ├── JsonRpc │ ├── RpcNotification.cs │ ├── RpcRequest.cs │ └── RpcResponse.cs ├── MessageReader.cs ├── MessageWriter.cs ├── Peachpie.LanguageServer.csproj ├── PhpLanguageServer.cs ├── Program.cs ├── ProjectHandler.cs ├── Properties │ └── AssemblyInfo.cs ├── Protocol │ ├── Diagnostic.cs │ ├── DidChangeTextDocumentParams.cs │ ├── DidChangeWatchedFilesParams.cs │ ├── DidOpenTextDocumentParams.cs │ ├── FileEvent.cs │ ├── Hover.cs │ ├── InitializeParams.cs │ ├── InitializeResult.cs │ ├── Location.cs │ ├── LogMessageParams.cs │ ├── MarkedString.cs │ ├── Position.cs │ ├── PublishDiagnosticsParams.cs │ ├── Range.cs │ ├── ServerCapabilities.cs │ ├── ShowMessageParams.cs │ ├── TextDocumentContentChangeEvent.cs │ ├── TextDocumentIdentifier.cs │ ├── TextDocumentItem.cs │ ├── TextDocumentPositionParams.cs │ └── VersionedTextDocumentIdentifier.cs ├── ServerOptions.cs ├── SourceSymbolSearcher.cs ├── StrongKeys │ └── core.snk ├── ToolTipInfo.cs ├── Utils │ ├── ILogTarget.cs │ ├── PathUtils.cs │ ├── ProjectUtils.cs │ ├── SpanUtils.cs │ └── ToolTipUtils.cs └── Workspaces │ └── XmlDocumentationProvider.cs └── Peachpie.VSCode ├── .gitignore ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── CHANGELOG.md ├── README.md ├── icons ├── peachpie-icon-new.png ├── peachpie-icon.png └── peachpie-vscode.png ├── images ├── README.md ├── Screen Shot 2017-04-17 at 14.59.19.png ├── breakpoint-cropped.png ├── breakpoint.png ├── create-project-cropped.png ├── create-project.png ├── debug-cropped.png ├── debug.png ├── diagnostics.gif ├── new-peachpie-project.gif ├── peachpie-icon.png ├── peachpie-round.png ├── project-icon.psd ├── round-orange-196x196.png ├── syntax-error.png ├── tEDLQt.gif ├── tooltips.gif ├── unresolved-diagnostics.png ├── vscode-diagnostics-long-cropped.png └── vscode-diagnostics-long.png ├── package-lock.json ├── package.json ├── publish.ps1 ├── src ├── defaults.ts └── extension.ts ├── templates ├── console.msbuildproj └── library.msbuildproj ├── test ├── extension.test.ts └── index.ts ├── tsconfig.json └── vsc-extension-quickstart.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | .vs/ 13 | 14 | # Build results 15 | [Dd]ebug/ 16 | [Dd]ebugPublic/ 17 | [Rr]elease/ 18 | [Rr]eleases/ 19 | x64/ 20 | x86/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Windows image file caches 26 | Thumbs.db 27 | ehthumbs.db 28 | 29 | # Folder config file 30 | Desktop.ini 31 | 32 | # Recycle Bin used on file shares 33 | $RECYCLE.BIN/ 34 | 35 | # Windows Installer files 36 | *.cab 37 | *.msi 38 | *.msm 39 | *.msp 40 | 41 | # Windows shortcuts 42 | *.lnk 43 | 44 | # ========================= 45 | # Operating System Files 46 | # ========================= 47 | 48 | # OSX 49 | # ========================= 50 | 51 | .DS_Store 52 | .AppleDouble 53 | .LSOverride 54 | 55 | # Thumbnails 56 | ._* 57 | 58 | # Files that might appear in the root of a volume 59 | .DocumentRevisions-V100 60 | .fseventsd 61 | .Spotlight-V100 62 | .TemporaryItems 63 | .Trashes 64 | .VolumeIcon.icns 65 | 66 | # Directories potentially created on remote AFP share 67 | .AppleDB 68 | .AppleDesktop 69 | Network Trash Folder 70 | Temporary Items 71 | .apdisk 72 | -------------------------------------------------------------------------------- /NuGet.Config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Peachpie.LanguageServer.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26228.9 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Peachpie.LanguageServer", "src\Peachpie.LanguageServer\Peachpie.LanguageServer.csproj", "{A6BBDC42-26B3-4510-8284-FCB648877C1B}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{D069240A-A4E3-4C05-B069-C19B7A31E642}" 9 | ProjectSection(SolutionItems) = preProject 10 | NuGet.Config = NuGet.Config 11 | EndProjectSection 12 | EndProject 13 | Global 14 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 15 | Debug|Any CPU = Debug|Any CPU 16 | Release|Any CPU = Release|Any CPU 17 | EndGlobalSection 18 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 19 | {A6BBDC42-26B3-4510-8284-FCB648877C1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 20 | {A6BBDC42-26B3-4510-8284-FCB648877C1B}.Debug|Any CPU.Build.0 = Debug|Any CPU 21 | {A6BBDC42-26B3-4510-8284-FCB648877C1B}.Release|Any CPU.ActiveCfg = Release|Any CPU 22 | {A6BBDC42-26B3-4510-8284-FCB648877C1B}.Release|Any CPU.Build.0 = Release|Any CPU 23 | EndGlobalSection 24 | GlobalSection(SolutionProperties) = preSolution 25 | HideSolutionNode = FALSE 26 | EndGlobalSection 27 | GlobalSection(ExtensibilityGlobals) = postSolution 28 | SolutionGuid = {4756084D-827B-4466-80DA-6895D92AEFC2} 29 | EndGlobalSection 30 | EndGlobal 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

3 | 4 |

5 | 6 | # Peachpie Visual Studio Code Extension 7 | This repository contains the projects necessary for the integration into Visual Studio Code. 8 | See the [repository of Peachpie - The PHP Compiler and Runtime for .NET](https://github.com/peachpiecompiler/peachpie) for more information about the whole project. 9 | 10 | ## Folder Structure 11 | 12 | - [src/Peachpie.VSCode](https://github.com/iolevel/peachpie-vscode/tree/master/src/Peachpie.VSCode) contains the Typescript Visual Studio Code Extension 13 | 14 | 15 | # Installing the Extension 16 | You can find the Peachpie extension for VSCode in the [marketplace](https://marketplace.visualstudio.com/items?itemName=iolevel.peachpie-vscode). 17 | 18 | Install it by launching VS Code Quick Open (Ctrl+P), then paste the following command, and press enter: ` ext install peachpie-vscode` 19 | -------------------------------------------------------------------------------- /samples/diagnostics/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": ".NET Core Launch (console)", 6 | "type": "coreclr", 7 | "request": "launch", 8 | "preLaunchTask": "build", 9 | "program": "${workspaceRoot}/bin/Debug/netcoreapp1.0/diagnostics.dll", 10 | "args": [], 11 | "cwd": "${workspaceRoot}", 12 | "externalConsole": false, 13 | "stopAtEntry": false, 14 | "internalConsoleOptions": "openOnSessionStart" 15 | }, 16 | { 17 | "name": ".NET Core Attach", 18 | "type": "coreclr", 19 | "request": "attach", 20 | "processId": "${command.pickProcess}" 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /samples/diagnostics/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0", 3 | "command": "dotnet", 4 | "isShellCommand": true, 5 | "args": [], 6 | "tasks": [ 7 | { 8 | "taskName": "build", 9 | "args": [ 10 | "${workspaceRoot}/project.json" 11 | ], 12 | "isBuildCommand": true, 13 | "problemMatcher": "$msCompile" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /samples/diagnostics/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "diagnostics", 3 | "version": "1.0.0", 4 | "description": "Demonstration of Peachpie diagnostics", 5 | "buildOptions": { 6 | "compilerName": "php", 7 | "compile": "**/*.php", 8 | "emitEntryPoint": true, 9 | "debugType": "portable" 10 | }, 11 | "dependencies": { 12 | "Peachpie.App": "0.5.0-preview5" 13 | }, 14 | "tools": { 15 | "Peachpie.Compiler.Tools": "0.5.0-preview5" 16 | }, 17 | "frameworks": { 18 | "netcoreapp1.0": { 19 | "dependencies": { 20 | "Microsoft.NETCore.App": { 21 | "type": "platform", 22 | "version": "1.0.0" 23 | } 24 | } 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /samples/diagnostics/undefined-types-members.php: -------------------------------------------------------------------------------- 1 | property = $goodInstance->property + 1; 41 | $goodInstance->method(); 42 | 43 | if ($goodInstance instanceof DefinedClass) {} 44 | 45 | try { 46 | } catch (DefinedException $exception) { 47 | } 48 | 49 | // Undefined types - diagnostics will appear for Undefined* (and nothing else) 50 | 51 | $b = undefinedFunction(5, 4); 52 | $b = UndefinedClass::Constant; 53 | $b = UndefinedClass::$staticProperty; 54 | $b = UndefinedClass::staticMethod(); 55 | $b = UndefinedClass::undefinedStaticMethod(); 56 | 57 | $badInstance = new UndefinedClass(); 58 | $badInstance->property = $badInstance->property + 1; 59 | $badInstance->method(); 60 | 61 | if ($badInstance instanceof UndefinedClass) {} 62 | 63 | try { 64 | } catch (UndefinedException $exception) { 65 | } 66 | 67 | // Unimplemented - diagnostics might appear in the future 68 | 69 | $b = DefinedClass::UndefinedStaticMethod(); 70 | 71 | $goodInstance->UndefinedMethod(); 72 | $goodInstance->undefinedProperty = $goodInstance->undefinedProperty + 1; 73 | 74 | $className = "OtherUndefinedClass"; 75 | $d = new $className(); 76 | -------------------------------------------------------------------------------- /samples/diagnostics/undefined-variables.php: -------------------------------------------------------------------------------- 1 | 0) { 20 | $maybeUndefined = 0; 21 | } 22 | echo $maybeUndefined; 23 | } 24 | 25 | bar(3); 26 | -------------------------------------------------------------------------------- /src/Peachpie.LanguageServer/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Attach", 9 | "type": "coreclr", 10 | "request": "attach", 11 | "processId": "${command:pickProcess}" 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /src/Peachpie.LanguageServer/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/Peachpie.LanguageServer.csproj" 11 | ], 12 | "problemMatcher": "$msCompile" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /src/Peachpie.LanguageServer/CompilationDiagnosticBroker.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using Pchp.CodeAnalysis; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace Peachpie.LanguageServer 9 | { 10 | public class CompilationDiagnosticBroker 11 | { 12 | // How long to wait before running the analysis after a modification 13 | private const int MillisecondsDelay = 1000; 14 | 15 | private readonly Action> _resultHandler; 16 | private Task> _diagnosticTask; 17 | 18 | public CompilationDiagnosticBroker(Action> resultHandler) 19 | { 20 | _resultHandler = resultHandler; 21 | } 22 | 23 | public PhpCompilation Compilation { get; private set; } 24 | 25 | public PhpCompilation LastAnalysedCompilation { get; private set; } 26 | 27 | public async void UpdateCompilation(PhpCompilation updatedCompilation) 28 | { 29 | Compilation = updatedCompilation; 30 | 31 | await AnalyseLazily(updatedCompilation); 32 | } 33 | 34 | private async Task AnalyseLazily(PhpCompilation updatedCompilation) 35 | { 36 | await Task.Delay(MillisecondsDelay); 37 | 38 | // Don't run the analysis if the compilation was modified since then (to enforce the delay) 39 | // or it is already running (to prevent running two tasks simultaneously) 40 | if (this.Compilation == updatedCompilation 41 | && (_diagnosticTask == null || _diagnosticTask.IsCompleted)) 42 | { 43 | var analysedCompilation = this.Compilation; 44 | try 45 | { 46 | _diagnosticTask = analysedCompilation.BindAndAnalyseTask(); 47 | 48 | var diagnostics = await _diagnosticTask; 49 | this.LastAnalysedCompilation = analysedCompilation; 50 | _resultHandler(diagnostics); 51 | } 52 | catch (DllNotFoundException) 53 | { 54 | 55 | } 56 | 57 | // If the compilation was changed during the analysis, attempt to run it again (with respect to the delay), 58 | // as it was blocked by the previous condition 59 | if (this.Compilation != updatedCompilation) 60 | { 61 | await AnalyseLazily(this.Compilation); 62 | } 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Peachpie.LanguageServer/EnvironmentUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Text; 6 | using System.Text.RegularExpressions; 7 | using System.Threading.Tasks; 8 | 9 | namespace Peachpie.LanguageServer 10 | { 11 | public static class EnvironmentUtils 12 | { 13 | const string DOTNET_CLI_UI_LANGUAGE = "DOTNET_CLI_UI_LANGUAGE"; 14 | 15 | public static string NetCoreRuntimePath { get; private set; } 16 | 17 | public static string MSBuildSDKsPath { get; private set; } 18 | 19 | /// 20 | /// Initializes the properties. 21 | /// 22 | public static async Task InitializeAsync() 23 | { 24 | if (NetCoreRuntimePath != null) 25 | { 26 | return; 27 | } 28 | 29 | NetCoreRuntimePath = await GetNetCorePathAsync(); 30 | 31 | MSBuildSDKsPath = Environment.GetEnvironmentVariable("MSBuildSDKsPath"); 32 | if (MSBuildSDKsPath == null) 33 | { 34 | MSBuildSDKsPath = Path.Combine(NetCoreRuntimePath, "Sdks"); 35 | } 36 | } 37 | 38 | private static async Task GetNetCorePathAsync() 39 | { 40 | // Ensure that we set the DOTNET_CLI_UI_LANGUAGE environment variable to "en-US" before 41 | // running 'dotnet --info'. Otherwise, we may get localized results. 42 | string originalCliLanguage = Environment.GetEnvironmentVariable(DOTNET_CLI_UI_LANGUAGE); 43 | Environment.SetEnvironmentVariable(DOTNET_CLI_UI_LANGUAGE, "en-US"); 44 | 45 | using (var process = new Process() 46 | { 47 | StartInfo = 48 | { 49 | UseShellExecute = false, 50 | RedirectStandardOutput = true, 51 | RedirectStandardError = true, 52 | CreateNoWindow = true, 53 | FileName = "dotnet", 54 | Arguments = "--info" 55 | }, 56 | }) 57 | { 58 | try 59 | { 60 | process.Start(); 61 | } 62 | catch 63 | { 64 | // no `dotnet` 65 | return null; 66 | } 67 | finally 68 | { 69 | Environment.SetEnvironmentVariable(DOTNET_CLI_UI_LANGUAGE, originalCliLanguage); 70 | } 71 | 72 | var output = await process.StandardOutput.ReadToEndAsync(); 73 | 74 | if (!string.IsNullOrEmpty(output)) 75 | { 76 | var regex = new Regex("Base Path:(.+)"); 77 | var matches = regex.Match(output); 78 | if (matches.Groups.Count >= 2) 79 | { 80 | return matches.Groups[1].Value.Trim(); 81 | } 82 | } 83 | } 84 | 85 | return null; 86 | } 87 | 88 | private static void ProcessDotnetInfoOutput(string output, ref TaskCompletionSource taskSource) 89 | { 90 | if (!string.IsNullOrEmpty(output)) 91 | { 92 | var regex = new Regex("Base Path:(.+)"); 93 | var matches = regex.Match(output); 94 | if (matches.Groups.Count >= 2) 95 | { 96 | string result = matches.Groups[1].Value.Trim(); 97 | taskSource.SetResult(result); 98 | 99 | return; 100 | } 101 | } 102 | 103 | taskSource.SetException(new IOException("Cannot obtain the base path of .NET Core")); 104 | } 105 | 106 | public static Dictionary GetCoreGlobalProperties(string projectPath, string toolsPath) 107 | { 108 | string solutionDir = Path.GetDirectoryName(projectPath); 109 | string extensionsPath = toolsPath; 110 | string sdksPath = MSBuildSDKsPath; 111 | string roslynTargetsPath = Path.Combine(toolsPath, "Roslyn"); 112 | 113 | return new Dictionary 114 | { 115 | { "SolutionDir", solutionDir }, 116 | { "MSBuildExtensionsPath", extensionsPath }, 117 | { "MSBuildSDKsPath", sdksPath }, 118 | { "RoslynTargetsPath", roslynTargetsPath }, 119 | { "DesignTimeBuild", "true" }, 120 | //{ "SkipCompilerExecution", "true" }, 121 | //{ "ProvideCommandLineArgs", "true" }, 122 | }; 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/Peachpie.LanguageServer/JsonRpc/RpcNotification.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Linq; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace Peachpie.LanguageServer.JsonRpc 9 | { 10 | [JsonObject] 11 | internal class RpcNotification 12 | { 13 | [JsonProperty("method")] 14 | public string Method { get; set; } 15 | 16 | [JsonProperty("params")] 17 | public JObject Params { get; set; } 18 | } 19 | 20 | [JsonObject] 21 | internal class RpcNotification 22 | { 23 | [JsonProperty("method")] 24 | public string Method { get; set; } 25 | 26 | [JsonProperty("params")] 27 | public T Params { get; set; } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Peachpie.LanguageServer/JsonRpc/RpcRequest.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Linq; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace Peachpie.LanguageServer.JsonRpc 9 | { 10 | [JsonObject] 11 | internal class RpcRequest 12 | { 13 | [JsonProperty("id")] 14 | public object Id { get; set; } 15 | 16 | [JsonProperty("method")] 17 | public string Method { get; set; } 18 | 19 | [JsonProperty("params")] 20 | public JObject Params { get; set; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Peachpie.LanguageServer/JsonRpc/RpcResponse.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace Peachpie.LanguageServer.JsonRpc 8 | { 9 | [JsonObject] 10 | internal class RpcResponse 11 | { 12 | [JsonProperty("id")] 13 | public object Id { get; set; } 14 | 15 | [JsonProperty("result")] 16 | public object Result { get; set; } 17 | 18 | [JsonProperty("error", NullValueHandling = NullValueHandling.Ignore)] 19 | public object Error { get; set; } 20 | } 21 | 22 | [JsonObject] 23 | internal class RpcResponse 24 | { 25 | [JsonProperty("id")] 26 | public object Id { get; set; } 27 | 28 | [JsonProperty("result")] 29 | public T Result { get; set; } 30 | 31 | [JsonProperty("error", NullValueHandling = NullValueHandling.Ignore)] 32 | public object Error { get; set; } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Peachpie.LanguageServer/MessageReader.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Peachpie.LanguageServer.JsonRpc; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Diagnostics; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | 12 | namespace Peachpie.LanguageServer 13 | { 14 | internal class MessageReader 15 | { 16 | private Stream _inputStream; 17 | 18 | public MessageReader(Stream inputStream) 19 | { 20 | _inputStream = inputStream; 21 | } 22 | 23 | public async Task ReadRequestAsync() 24 | { 25 | // TODO: Handle format errors (such as missing Content-Length) 26 | int? contentLength = await ReadHeader(); 27 | 28 | var buffer = new byte[contentLength.Value]; 29 | int readBytes = 0; 30 | while (readBytes < contentLength.Value) 31 | { 32 | readBytes += await _inputStream.ReadAsync(buffer, readBytes, contentLength.Value - readBytes); 33 | } 34 | 35 | var requestJson = Encoding.UTF8.GetString(buffer); 36 | var request = JsonConvert.DeserializeObject(requestJson); 37 | 38 | return request; 39 | } 40 | 41 | private async Task ReadHeader() 42 | { 43 | int? contentLength = null; 44 | 45 | var lineBytes = new List(); 46 | 47 | // We use this workaround to perform the read of the next request asynchronously - there might be 48 | // significant delays between the requests and we do not want to block the thread during them. 49 | var singleByteBuffer = new byte[1]; 50 | await _inputStream.ReadAsync(singleByteBuffer, 0, 1); 51 | int currentByte = singleByteBuffer[0]; 52 | 53 | while (true) 54 | { 55 | if (currentByte == -1) 56 | { 57 | throw new IOException("Unexpected end of the stream in the request header"); 58 | } 59 | else if (currentByte == '\r') 60 | { 61 | if (_inputStream.ReadByte() != '\n') 62 | { 63 | throw new IOException(@"Invalid end of the line, \r\n expected"); 64 | } 65 | 66 | string line = Encoding.ASCII.GetString(lineBytes.ToArray()); 67 | lineBytes.Clear(); 68 | 69 | if (line == "") 70 | { 71 | break; 72 | } 73 | 74 | var tokens = line.Split(new string[] { ": " }, StringSplitOptions.None); 75 | if (tokens[0] == "Content-Length") 76 | { 77 | contentLength = int.Parse(tokens[1]); 78 | } 79 | } 80 | else 81 | { 82 | lineBytes.Add((byte)currentByte); 83 | } 84 | 85 | currentByte = _inputStream.ReadByte(); 86 | } 87 | 88 | return contentLength; 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/Peachpie.LanguageServer/MessageWriter.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Peachpie.LanguageServer.JsonRpc; 3 | using System.IO; 4 | using System.Text; 5 | 6 | namespace Peachpie.LanguageServer 7 | { 8 | internal class MessageWriter 9 | { 10 | private Stream _outputStream; 11 | 12 | public MessageWriter(Stream outputStream) 13 | { 14 | _outputStream = outputStream; 15 | } 16 | 17 | public void WriteResponse(object requestId, T result) 18 | { 19 | var response = new RpcResponse() 20 | { 21 | Id = requestId, 22 | Result = result 23 | }; 24 | 25 | SerializeAndSend(response); 26 | } 27 | 28 | public void WriteNotification(string method, T parameters) 29 | { 30 | var notification = new RpcNotification() 31 | { 32 | Method = method, 33 | Params = parameters 34 | }; 35 | 36 | SerializeAndSend(notification); 37 | } 38 | 39 | private void SerializeAndSend(T data) 40 | { 41 | lock (this) 42 | { 43 | var dataText = JsonConvert.SerializeObject(data); 44 | var dataBytes = Encoding.UTF8.GetBytes(dataText); 45 | 46 | var headerText = $"Content-Length: {dataBytes.Length}\r\n\r\n"; 47 | var headerBytes = Encoding.ASCII.GetBytes(headerText); 48 | 49 | _outputStream.Write(headerBytes, 0, headerBytes.Length); 50 | _outputStream.Write(dataBytes, 0, dataBytes.Length); 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Peachpie.LanguageServer/Peachpie.LanguageServer.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 1.0.23 5 | net5.0 6 | Peachpie.LanguageServer 7 | latest 8 | Exe 9 | Peachpie.LanguageServer 10 | false 11 | false 12 | false 13 | True 14 | StrongKeys\core.snk 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/Peachpie.LanguageServer/PhpLanguageServer.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using Newtonsoft.Json; 3 | using Pchp.CodeAnalysis; 4 | using Peachpie.LanguageServer.Protocol; 5 | using Peachpie.LanguageServer.Utils; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Collections.Immutable; 9 | using System.Diagnostics; 10 | using System.IO; 11 | using System.Linq; 12 | using System.Threading.Tasks; 13 | 14 | namespace Peachpie.LanguageServer 15 | { 16 | internal class PhpLanguageServer : ILogTarget 17 | { 18 | private const string DiagnosticSource = "peachpie"; 19 | 20 | private ServerOptions _options; 21 | private MessageReader _requestReader; 22 | private MessageWriter _messageWriter; 23 | 24 | private string _rootPath; 25 | private ProjectHandler _project; 26 | 27 | static Version LatestPeachpieVersion 28 | { 29 | get 30 | { 31 | var v4 = typeof(PhpCompilation).Assembly.GetName().Version; 32 | return new Version(v4.Major, v4.Minor, v4.Build); // v3 33 | } 34 | } 35 | 36 | public PhpLanguageServer(ServerOptions options, MessageReader requestReader, MessageWriter messageWriter) 37 | { 38 | _options = options; 39 | _requestReader = requestReader; 40 | _messageWriter = messageWriter; 41 | } 42 | 43 | public async Task Run() 44 | { 45 | while (true) 46 | { 47 | var request = await _requestReader.ReadRequestAsync(); 48 | //if (_options.IsDebug) 49 | //{ 50 | // SendLogMessage($"Received: {JsonConvert.SerializeObject(request)}"); 51 | //} 52 | 53 | switch (request.Method) 54 | { 55 | // initialize 56 | 57 | case "initialize": 58 | var initializeParams = request.Params.ToObject(); 59 | SendInitializationResponse(request); 60 | await OpenFolder(initializeParams.RootPath); 61 | break; 62 | case "initialized": 63 | // ignore 64 | break; 65 | 66 | // shutdown/exit 67 | 68 | case "shutdown": 69 | _project?.Dispose(); 70 | _messageWriter.WriteResponse(request.Id, null); // empty response 71 | break; // not exit the process 72 | 73 | case "exit": 74 | _project?.Dispose(); 75 | return; // exit the process 76 | 77 | // workspace 78 | 79 | case "workspace/didChangeWatchedFiles": 80 | var changeWatchedParams = request.Params.ToObject(); 81 | await ProcessFileChangesAsync(changeWatchedParams); 82 | break; 83 | 84 | // textDocument 85 | 86 | case "textDocument/didOpen": 87 | var openParams = request.Params.ToObject(); 88 | // TODO: Decide how to handle opened files that are not in the current folder 89 | break; 90 | case "textDocument/didChange": 91 | await ProcessDocumentChanges(request.Params.ToObject()); 92 | break; 93 | case "textDocument/hover": 94 | ProcessHover(request.Id, request.Params.ToObject()); 95 | break; 96 | case "textDocument/definition": 97 | ProcessGoToDefinition(request.Id, request.Params.ToObject()); 98 | break; 99 | case "textDocument/didClose": 100 | // ignored 101 | break; 102 | case "textDocument/didSave": 103 | // ignored 104 | break; 105 | 106 | // 107 | 108 | default: 109 | if (request.Method.StartsWith("$/")) 110 | { 111 | // ignored 112 | break; 113 | } 114 | 115 | SendLogMessage($"Request '{request.Method}' was unhandled."); 116 | break; 117 | } 118 | } 119 | } 120 | 121 | private async Task OpenFolder(string rootPath) 122 | { 123 | if (rootPath == null) 124 | { 125 | return; 126 | } 127 | 128 | _rootPath = PathUtils.NormalizePath(rootPath); 129 | 130 | await TryReloadProjectAsync(); 131 | } 132 | 133 | private async Task ProcessFileChangesAsync(DidChangeWatchedFilesParams changeWatchedParams) 134 | { 135 | // We now watch only .msbuildproj and project.assets.json files, forcing us to reload the project 136 | await TryReloadProjectAsync(); 137 | } 138 | 139 | private async Task TryReloadProjectAsync() 140 | { 141 | if (_rootPath == null) 142 | { 143 | return; 144 | } 145 | 146 | ProjectHandler newProject = null; 147 | 148 | try 149 | { 150 | newProject = await ProjectUtils.TryGetFirstPhpProjectAsync(_rootPath, this); 151 | } 152 | catch (Exception ex) 153 | { 154 | SendLogMessage(ex.ToString()); 155 | } 156 | 157 | if (newProject == null) 158 | { 159 | return; 160 | } 161 | 162 | // dispose previous one 163 | _project?.Dispose(); 164 | 165 | // new project 166 | _project = newProject; 167 | _project.DocumentDiagnosticsChanged += DocumentDiagnosticsChanged; 168 | _project.Initialize(); 169 | 170 | // check the Sdk version 171 | newProject.TryGetSdkVersion(out var sdkverstr); 172 | 173 | SendLogMessage($@"Loaded project: 174 | {newProject.BuildInstance.FullPath} 175 | PeachPie Version: {(sdkverstr ?? "unknown")}"); 176 | 177 | if (Version.TryParse(sdkverstr, out var sdkver) && sdkver < LatestPeachpieVersion) 178 | { 179 | SendLogMessage($" New version available: {LatestPeachpieVersion}"); 180 | ShowMessage($"PeachPie {LatestPeachpieVersion} is available! Your project is running version {sdkver.ToString(3)}, please update."); 181 | } 182 | } 183 | 184 | private async Task ProcessDocumentChanges(DidChangeTextDocumentParams changeParams) 185 | { 186 | if (_project == null) 187 | { 188 | await TryReloadProjectAsync(); 189 | } 190 | 191 | if (_project != null) 192 | { 193 | string path = PathUtils.NormalizePath(changeParams.TextDocument.Uri); 194 | 195 | // Don't care about the documents outside the current folder if it's opened 196 | if (_rootPath != null && !path.StartsWith(_rootPath)) 197 | { 198 | return; 199 | } 200 | 201 | // Similarly, ignore files outside the active project if opened 202 | if (!path.StartsWith(_project.RootPath)) 203 | { 204 | return; 205 | } 206 | 207 | // For now, only the full document synchronization works 208 | string text = changeParams.ContentChanges[0].Text; 209 | 210 | try 211 | { 212 | _project.UpdateFile(path, text); 213 | } 214 | catch (Exception ex) 215 | { 216 | SendLogMessage(ex.ToString()); 217 | } 218 | } 219 | } 220 | 221 | private void ProcessGoToDefinition(object requestId, TextDocumentPositionParams docPosition) 222 | { 223 | // result: Location | Location[] | LocationLink[] | null 224 | 225 | Protocol.Location[] result = null; 226 | 227 | if (_project != null) 228 | { 229 | string filepath = PathUtils.NormalizePath(docPosition.TextDocument.Uri); 230 | try 231 | { 232 | var locations = _project.ObtainDefinition(filepath, docPosition.Position.Line, docPosition.Position.Character); 233 | result = locations?.ToArray(); 234 | } 235 | catch (NotImplementedException) { } // might not be implemented 236 | catch (NotSupportedException) { } 237 | catch (AggregateException) { } 238 | } 239 | 240 | _messageWriter.WriteResponse(requestId, result); 241 | } 242 | 243 | private void ProcessHover(object requestId, TextDocumentPositionParams hoverParams) 244 | { 245 | ToolTipInfo tooltip; 246 | 247 | if (_project != null) 248 | { 249 | string filepath = PathUtils.NormalizePath(hoverParams.TextDocument.Uri); 250 | tooltip = _project.ObtainToolTip(filepath, hoverParams.Position.Line, hoverParams.Position.Character); 251 | } 252 | else 253 | { 254 | tooltip = null; 255 | } 256 | 257 | Hover response; 258 | 259 | if (tooltip != null) 260 | { 261 | var codePart = new MarkedString() 262 | { 263 | Language = "php", 264 | Value = tooltip.Code 265 | }; 266 | 267 | response = new Hover() 268 | { 269 | Contents = (tooltip.Description != null) ? 270 | new object[] { codePart, tooltip.Description } 271 | : new object[] { codePart } 272 | }; 273 | } 274 | else 275 | { 276 | // Return empty response to hide the "Loading..." text in the box 277 | response = null; 278 | } 279 | 280 | _messageWriter.WriteResponse(requestId, response); 281 | } 282 | 283 | private void SendInitializationResponse(JsonRpc.RpcRequest request) 284 | { 285 | var initializeResult = new InitializeResult() 286 | { 287 | Capabilities = new ServerCapabilities() 288 | { 289 | TextDocumentSync = TextDocumentSyncKind.Full, // TOOD: change to incremental to improve perf. 290 | HoverProvider = true, 291 | DefinitionProvider = true, 292 | } 293 | }; 294 | _messageWriter.WriteResponse(request.Id, initializeResult); 295 | 296 | SendLogMessage($@" 297 | PeachPie Language Server 298 | PID: {Process.GetCurrentProcess().Id} 299 | Path: {System.Reflection.Assembly.GetEntryAssembly().Location} 300 | "); 301 | 302 | if (_options.IsDebug) 303 | { 304 | SendGreetingMessage(); 305 | } 306 | } 307 | 308 | private void SendGreetingMessage() 309 | { 310 | int processId = Process.GetCurrentProcess().Id; 311 | ShowMessage($"Hello from PeachPie Language Server! The ID of the process is {processId}"); 312 | } 313 | 314 | private void ShowMessage(string message) 315 | { 316 | var showMessageParams = new ShowMessageParams() 317 | { 318 | Message = message, 319 | // An information message 320 | // TODO: Introduce an enum for this 321 | Type = 3 322 | }; 323 | _messageWriter.WriteNotification("window/showMessage", showMessageParams); 324 | } 325 | 326 | private void SendLogMessage(string text) 327 | { 328 | var logMessageParams = new LogMessageParams() 329 | { 330 | Message = text, 331 | // A log message 332 | // TODO: Introduce an enum for this 333 | Type = 4 334 | }; 335 | _messageWriter.WriteNotification("window/logMessage", logMessageParams); 336 | } 337 | 338 | private void DocumentDiagnosticsChanged(object sender, ProjectHandler.DocumentDiagnosticsEventArgs e) 339 | { 340 | var diagnosticsParams = new PublishDiagnosticsParams() 341 | { 342 | Uri = new Uri(e.DocumentPath).AbsoluteUri, 343 | Diagnostics = e.Diagnostics 344 | .Select(diagnostic => new Protocol.Diagnostic() 345 | { 346 | Range = diagnostic.Location.AsRange(), 347 | Severity = ConvertSeverity(diagnostic.Severity), 348 | Code = diagnostic.Id, 349 | Source = "PeachPie", 350 | Message = diagnostic.GetMessage(), 351 | }), 352 | }; 353 | 354 | _messageWriter.WriteNotification("textDocument/publishDiagnostics", diagnosticsParams); 355 | } 356 | 357 | private static int? ConvertSeverity(DiagnosticSeverity severity) 358 | { 359 | // TODO: Introduce an enum for this 360 | switch (severity) 361 | { 362 | case DiagnosticSeverity.Error: 363 | return 1; 364 | case DiagnosticSeverity.Warning: 365 | return 2; 366 | case DiagnosticSeverity.Info: 367 | return 3; 368 | case DiagnosticSeverity.Hidden: 369 | default: 370 | return null; 371 | } 372 | } 373 | 374 | void ILogTarget.LogMessage(string message) => this.SendLogMessage(message); 375 | } 376 | } -------------------------------------------------------------------------------- /src/Peachpie.LanguageServer/Program.cs: -------------------------------------------------------------------------------- 1 | using Peachpie.LanguageServer.Protocol; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Linq; 6 | using System.Reflection; 7 | using System.Runtime.Loader; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | 11 | namespace Peachpie.LanguageServer 12 | { 13 | public class Program 14 | { 15 | public static void Main(string[] args) 16 | { 17 | MainAsync(args).Wait(); 18 | } 19 | 20 | private static async Task MainAsync(string[] args) 21 | { 22 | await EnvironmentUtils.InitializeAsync(); 23 | 24 | var options = ServerOptions.ParseFromArguments(args); 25 | 26 | var requestReader = new MessageReader(Console.OpenStandardInput()); 27 | var messageWriter = new MessageWriter(Console.OpenStandardOutput()); 28 | 29 | AssemblyLoadContext.Default.Resolving += Assembly_Resolving; 30 | 31 | var server = new PhpLanguageServer(options, requestReader, messageWriter); 32 | await server.Run(); 33 | } 34 | 35 | private static Assembly Assembly_Resolving(AssemblyLoadContext ctx, AssemblyName name) 36 | { 37 | // I'm not proud with this but we need to get things working quickly 38 | // ... we should not reference those packages at all and make use of msbuild on user's machine 39 | 40 | // Sdk is attempting to load its versions from actual NuGetSdkResolver, but we have a different version, 41 | // and it does not load the ones located in 'C:\Program Files\dotnet\sdk' by itself ... 42 | 43 | if (name.Name == "NuGet.ProjectModel") 44 | { 45 | return typeof(NuGet.ProjectModel.BuildOptions).Assembly; // our version :/ 46 | } 47 | if (name.Name == "NuGet.Frameworks") 48 | { 49 | return typeof(NuGet.Frameworks.NuGetFramework).Assembly; // our version :/ 50 | } 51 | if (name.Name == "NuGet.Common") 52 | { 53 | return typeof(NuGet.Common.FileUtility).Assembly; // our version :/ 54 | } 55 | if (name.Name == "NuGet.Packaging") 56 | { 57 | return typeof(NuGet.Packaging.FrameworkReference).Assembly; // our version :/ 58 | } 59 | if (name.Name == "NuGet.Versioning") 60 | { 61 | return typeof(NuGet.Versioning.SemanticVersion).Assembly; // our version :/ 62 | } 63 | 64 | return null; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Peachpie.LanguageServer/ProjectHandler.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Build.Execution; 2 | using Microsoft.CodeAnalysis; 3 | using Pchp.CodeAnalysis; 4 | using Pchp.CodeAnalysis.Utilities; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Collections.Immutable; 8 | using System.Linq; 9 | using System.Text; 10 | using Pchp.CodeAnalysis.Semantics; 11 | using Pchp.CodeAnalysis.Symbols; 12 | using Devsense.PHP.Syntax.Ast; 13 | using Devsense.PHP.Text; 14 | using Pchp.CodeAnalysis.FlowAnalysis; 15 | using Microsoft.CodeAnalysis.Text; 16 | using System.Collections.Concurrent; 17 | 18 | namespace Peachpie.LanguageServer 19 | { 20 | class ProjectHandler : IDisposable 21 | { 22 | public class DocumentDiagnosticsEventArgs : EventArgs 23 | { 24 | public string DocumentPath { get; } 25 | 26 | public IEnumerable Diagnostics { get; } 27 | 28 | public DocumentDiagnosticsEventArgs(string documentPath, IEnumerable diagnostics) 29 | { 30 | this.DocumentPath = documentPath; 31 | this.Diagnostics = diagnostics; 32 | } 33 | } 34 | 35 | readonly CompilationDiagnosticBroker _diagnosticBroker; 36 | 37 | readonly ConcurrentDictionary> _parserDiagnostics 38 | = new ConcurrentDictionary>(StringComparer.InvariantCultureIgnoreCase); 39 | 40 | private HashSet _filesWithSemanticDiagnostics 41 | = new HashSet(); 42 | 43 | public PhpCompilation Compilation => _diagnosticBroker.Compilation; 44 | 45 | public ProjectInstance BuildInstance { get; } 46 | 47 | public Encoding SourceEncoding { get; } 48 | 49 | public string RootPath => PathUtils.NormalizePath(BuildInstance.Directory); 50 | 51 | public event EventHandler DocumentDiagnosticsChanged; 52 | 53 | public ProjectHandler(PhpCompilation compilation, ProjectInstance buildInstance, Encoding encoding) 54 | { 55 | BuildInstance = buildInstance; 56 | _diagnosticBroker = new CompilationDiagnosticBroker(HandleCompilationDiagnostics); 57 | _diagnosticBroker.UpdateCompilation(compilation); 58 | SourceEncoding = encoding ?? Encoding.UTF8; 59 | } 60 | 61 | public void Initialize() 62 | { 63 | // Initially populate _parserDiagnostics and send the corresponding diagnostics 64 | // (_parserDiagnostics will be updated by _diagnosticBroker) 65 | 66 | var diagnostics = Compilation.GetParseDiagnostics(); 67 | foreach (var fileDiagnostics in diagnostics.GroupBy(diag => diag.Location.SourceTree)) 68 | { 69 | var path = fileDiagnostics.Key.FilePath; 70 | _parserDiagnostics[path] = fileDiagnostics.ToImmutableArray(); 71 | 72 | OnDocumentDiagnosticsChanged(path, fileDiagnostics); 73 | } 74 | } 75 | 76 | public void UpdateFile(string path, string text) 77 | { 78 | _parserDiagnostics.TryGetValue(path, out var previousDiagnostics); 79 | 80 | var syntaxTree = PhpSyntaxTree.ParseCode(SourceText.From(text, SourceEncoding), PhpParseOptions.Default, PhpParseOptions.Default, path); 81 | 82 | if (syntaxTree.Diagnostics.Length != 0) 83 | { 84 | _parserDiagnostics[path] = syntaxTree.Diagnostics; 85 | } 86 | else if (previousDiagnostics != null) 87 | { 88 | _parserDiagnostics.TryRemove(path, out _); 89 | } 90 | 91 | if (syntaxTree.Diagnostics.Length != 0 || previousDiagnostics != null) 92 | { 93 | // If there were any errors previously, send an empty set to remove them 94 | OnDocumentDiagnosticsChanged(path, syntaxTree.Diagnostics); 95 | } 96 | 97 | // Update the compilation 98 | if (syntaxTree.Diagnostics.All(d => d.Severity != DiagnosticSeverity.Error) && _diagnosticBroker.Compilation != null) 99 | { 100 | var currentTree = _diagnosticBroker.Compilation.SyntaxTrees.FirstOrDefault(tree => tree.FilePath == path); 101 | 102 | var updatedCompilation = currentTree == null 103 | ? (PhpCompilation)_diagnosticBroker.Compilation.AddSyntaxTrees(syntaxTree) 104 | : (PhpCompilation)_diagnosticBroker.Compilation.ReplaceSyntaxTree(currentTree, syntaxTree); 105 | 106 | _diagnosticBroker.UpdateCompilation(updatedCompilation); 107 | } 108 | } 109 | 110 | internal IEnumerable ObtainDefinition(string filepath, int line, int character) 111 | { 112 | var compilation = _diagnosticBroker.LastAnalysedCompilation; 113 | 114 | // We have to work with already fully analyzed and bound compilation that is up-to-date with the client's code 115 | if (compilation == null) 116 | { 117 | return Array.Empty(); 118 | } 119 | 120 | // Find the symbols gathered from the given source code 121 | return ToolTipUtils.ObtainDefinition(compilation, filepath, line, character); 122 | } 123 | 124 | public ToolTipInfo ObtainToolTip(string filepath, int line, int character) 125 | { 126 | var compilation = _diagnosticBroker.LastAnalysedCompilation; 127 | 128 | // We have to work with already fully analyzed and bound compilation that is up-to-date with the client's code 129 | if (compilation == null) 130 | { 131 | return null; 132 | } 133 | 134 | // Find the symbols gathered from the given source code 135 | return ToolTipUtils.ObtainToolTip(compilation, filepath, line, character); 136 | } 137 | 138 | bool IsHidden(Diagnostic d) 139 | { 140 | var options = Compilation.Options.SpecificDiagnosticOptions; 141 | 142 | return 143 | d.IsSuppressed || 144 | d.Severity == DiagnosticSeverity.Hidden || 145 | (options != null && options.TryGetValue(d.Id, out var report) && (report == ReportDiagnostic.Suppress || report == ReportDiagnostic.Hidden)); 146 | } 147 | 148 | private void HandleCompilationDiagnostics(IEnumerable diagnostics) 149 | { 150 | var errorFiles = new HashSet(); 151 | var options = Compilation.Options.SpecificDiagnosticOptions; 152 | 153 | var fileGroups = diagnostics 154 | .Where(d => !IsHidden(d)) 155 | .GroupBy(diagnostic => diagnostic.Location.SourceTree); 156 | 157 | foreach (var fileDiagnostics in fileGroups) 158 | { 159 | var file = fileDiagnostics.Key.FilePath; 160 | 161 | errorFiles.Add(file); 162 | 163 | OnDocumentDiagnosticsChanged( 164 | file, 165 | _parserDiagnostics.TryGetValue(file, out var parsediag) ? parsediag.Concat(fileDiagnostics) : fileDiagnostics); 166 | } 167 | 168 | var cleared = _filesWithSemanticDiagnostics.Except(errorFiles); 169 | foreach (var file in cleared) 170 | { 171 | OnDocumentDiagnosticsChanged( 172 | file, 173 | _parserDiagnostics.TryGetValue(file, out var parsediag) ? parsediag : ImmutableArray.Empty); 174 | } 175 | 176 | _filesWithSemanticDiagnostics = errorFiles; 177 | } 178 | 179 | private void OnDocumentDiagnosticsChanged(string documentPath, IEnumerable diagnostics) 180 | { 181 | DocumentDiagnosticsChanged?.Invoke(this, new DocumentDiagnosticsEventArgs(documentPath, diagnostics)); 182 | } 183 | 184 | /// 185 | /// Gets used PeachPie Sdk version. 186 | /// 187 | public bool TryGetSdkVersion(out string version) 188 | { 189 | version = this.BuildInstance.GetPropertyValue("PeachpieVersion"); 190 | return version != null; 191 | } 192 | 193 | public void Dispose() 194 | { 195 | this.DocumentDiagnosticsChanged = null; 196 | } 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /src/Peachpie.LanguageServer/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyConfiguration("")] 9 | [assembly: AssemblyCompany("")] 10 | [assembly: AssemblyProduct("Peachpie.LanguageServer")] 11 | [assembly: AssemblyTrademark("")] 12 | 13 | // Setting ComVisible to false makes the types in this assembly not visible 14 | // to COM components. If you need to access a type in this assembly from 15 | // COM, set the ComVisible attribute to true on that type. 16 | [assembly: ComVisible(false)] 17 | 18 | // The following GUID is for the ID of the typelib if this project is exposed to COM 19 | [assembly: Guid("a6bbdc42-26b3-4510-8284-fcb648877c1b")] 20 | -------------------------------------------------------------------------------- /src/Peachpie.LanguageServer/Protocol/Diagnostic.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace Peachpie.LanguageServer.Protocol 8 | { 9 | [JsonObject] 10 | internal class Diagnostic 11 | { 12 | [JsonProperty("range")] 13 | public Range Range { get; set; } 14 | 15 | [JsonProperty("severity")] 16 | public int? Severity { get; set; } 17 | 18 | [JsonProperty("code")] 19 | public object Code { get; set; } 20 | 21 | [JsonProperty("source")] 22 | public string Source { get; set; } 23 | 24 | [JsonProperty("message")] 25 | public string Message { get; set; } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Peachpie.LanguageServer/Protocol/DidChangeTextDocumentParams.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace Peachpie.LanguageServer.Protocol 8 | { 9 | [JsonObject] 10 | internal class DidChangeTextDocumentParams 11 | { 12 | [JsonProperty("textDocument")] 13 | public VersionedTextDocumentIdentifier TextDocument { get; set; } 14 | 15 | [JsonProperty("contentChanges")] 16 | public TextDocumentContentChangeEvent[] ContentChanges { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Peachpie.LanguageServer/Protocol/DidChangeWatchedFilesParams.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace Peachpie.LanguageServer.Protocol 8 | { 9 | [JsonObject] 10 | internal class DidChangeWatchedFilesParams 11 | { 12 | [JsonProperty("changes")] 13 | public FileEvent[] Changes { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Peachpie.LanguageServer/Protocol/DidOpenTextDocumentParams.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace Peachpie.LanguageServer.Protocol 8 | { 9 | [JsonObject] 10 | internal class DidOpenTextDocumentParams 11 | { 12 | [JsonProperty("textDocument")] 13 | public TextDocumentItem TextDocument { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Peachpie.LanguageServer/Protocol/FileEvent.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace Peachpie.LanguageServer.Protocol 8 | { 9 | internal enum FileChangeType 10 | { 11 | Created = 1, 12 | Changed = 2, 13 | Deleted = 3 14 | } 15 | 16 | [JsonObject] 17 | internal class FileEvent 18 | { 19 | [JsonProperty("uri")] 20 | public string Uri { get; set; } 21 | 22 | [JsonProperty("type")] 23 | public FileChangeType Type { get; set; } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Peachpie.LanguageServer/Protocol/Hover.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace Peachpie.LanguageServer.Protocol 7 | { 8 | [JsonObject] 9 | internal class Hover 10 | { 11 | /// 12 | /// Elements are either of type or . 13 | /// 14 | [JsonProperty("contents")] 15 | public object[] Contents { get; set; } 16 | 17 | [JsonProperty("range")] 18 | public Range? Range { get; set; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Peachpie.LanguageServer/Protocol/InitializeParams.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Linq; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace Peachpie.LanguageServer.Protocol 9 | { 10 | [JsonObject] 11 | internal class InitializeParams 12 | { 13 | [JsonProperty("processId")] 14 | public int? ProcessId { get; set; } 15 | 16 | [JsonProperty("rootPath")] 17 | public string RootPath { get; set; } 18 | 19 | [JsonProperty("rootUri")] 20 | public string RootUri { get; set; } 21 | 22 | [JsonProperty("initializationOptions")] 23 | public JObject InitializationOptions { get; set; } 24 | 25 | [JsonProperty("capabilities")] 26 | public JObject Capabilities { get; set; } 27 | 28 | // TODO: Turn into enum ('off' | 'messages' | 'verbose') 29 | [JsonProperty("trace")] 30 | public string Trace { get; set; } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Peachpie.LanguageServer/Protocol/InitializeResult.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace Peachpie.LanguageServer.Protocol 8 | { 9 | [JsonObject] 10 | internal class InitializeResult 11 | { 12 | [JsonProperty("capabilities")] 13 | public ServerCapabilities Capabilities { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Peachpie.LanguageServer/Protocol/Location.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace Peachpie.LanguageServer.Protocol 7 | { 8 | [JsonObject] 9 | struct Location 10 | { 11 | [JsonProperty("uri")] 12 | public string Uri { get; set; } 13 | 14 | [JsonProperty("range")] 15 | public Range Range { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Peachpie.LanguageServer/Protocol/LogMessageParams.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace Peachpie.LanguageServer.Protocol 8 | { 9 | [JsonObject] 10 | internal class LogMessageParams 11 | { 12 | [JsonProperty("type")] 13 | public int Type { get; set; } 14 | 15 | [JsonProperty("message")] 16 | public string Message { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Peachpie.LanguageServer/Protocol/MarkedString.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace Peachpie.LanguageServer.Protocol 7 | { 8 | [JsonObject] 9 | internal struct MarkedString 10 | { 11 | [JsonProperty("language")] 12 | public string Language { get; set; } 13 | 14 | [JsonProperty("value")] 15 | public string Value { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Peachpie.LanguageServer/Protocol/Position.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace Peachpie.LanguageServer.Protocol 8 | { 9 | [JsonObject] 10 | internal struct Position 11 | { 12 | public Position(int line, int character) 13 | { 14 | this.Line = line; 15 | this.Character = character; 16 | } 17 | 18 | [JsonProperty("line")] 19 | public int Line { get; set; } 20 | 21 | [JsonProperty("character")] 22 | public int Character { get; set; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Peachpie.LanguageServer/Protocol/PublishDiagnosticsParams.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace Peachpie.LanguageServer.Protocol 8 | { 9 | [JsonObject] 10 | internal class PublishDiagnosticsParams 11 | { 12 | [JsonProperty("uri")] 13 | public string Uri { get; set; } 14 | 15 | [JsonProperty("diagnostics")] 16 | public IEnumerable Diagnostics { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Peachpie.LanguageServer/Protocol/Range.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace Peachpie.LanguageServer.Protocol 8 | { 9 | [JsonObject] 10 | internal struct Range 11 | { 12 | public Range(Position start, Position end) 13 | { 14 | this.Start = start; 15 | this.End = end; 16 | } 17 | 18 | [JsonProperty("start")] 19 | public Position Start { get; set; } 20 | 21 | [JsonProperty("end")] 22 | public Position End { get; set; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Peachpie.LanguageServer/Protocol/ServerCapabilities.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace Peachpie.LanguageServer.Protocol 8 | { 9 | [JsonObject] 10 | class ServerCapabilities 11 | { 12 | [JsonProperty("textDocumentSync")] 13 | public TextDocumentSyncKind? TextDocumentSync { get; set; } 14 | 15 | [JsonProperty("hoverProvider")] 16 | public bool? HoverProvider { get; set; } 17 | 18 | [JsonProperty("completionProvider")] 19 | public CompletionOptions CompletionProvider { get; set; } 20 | 21 | [JsonProperty("referencesProvider")] 22 | public bool? ReferencesProvider { get; set; } 23 | 24 | [JsonProperty("definitionProvider")] 25 | public bool? DefinitionProvider { get; set; } 26 | 27 | [JsonProperty("documentSymbolProvider")] 28 | public bool? DocumentSymbolProvider { get; set; } 29 | 30 | [JsonProperty("workspaceSymbolProvider")] 31 | public bool? WorkspaceSymbolProvider { get; set; } 32 | 33 | /// 34 | /// Code Action options. 35 | /// 36 | public class CodeActionOptions 37 | { 38 | /// 39 | /// CodeActionKinds that this server may return. 40 | /// 41 | /// The list of kinds may be generic, such as `CodeActionKind.Refactor`, or the server 42 | /// may list out every specific kind they provide. 43 | /// 44 | public string[] codeActionKinds { get; set; } 45 | } 46 | 47 | /** 48 | * The server provides code actions. The `CodeActionOptions` return type is only 49 | * valid if the client signals code action literal support via the property 50 | * `textDocument.codeAction.codeActionLiteralSupport`. 51 | */ 52 | public CodeActionOptions codeActionProvider { get; set; } 53 | 54 | [JsonProperty("renameProvider")] 55 | public bool? RenameProvider { get; set; } 56 | 57 | [JsonProperty("colorProvider")] 58 | public bool? ColorProvider { get; set; } 59 | 60 | [JsonProperty("documentHighlightProvider")] 61 | public bool? DocumentHighlightProvider { get; set; } 62 | 63 | [JsonProperty("documentFormattingProvider")] 64 | public bool? DocumentFormattingProvider { get; set; } 65 | 66 | [JsonProperty("documentRangeFormattingProvider")] 67 | public bool? DocumentRangeFormattingProvider { get; set; } 68 | 69 | [JsonProperty("documentOnTypeFormattingProvider")] 70 | public DocumentOnTypeFormattingOptions DocumentOnTypeFormattingProvider { get; set; } 71 | 72 | [JsonProperty("signatureHelpProvider")] 73 | public SignatureHelpOptions SignatureHelpProvider { get; set; } 74 | 75 | /// 76 | /// The server provides folding provider support. 77 | /// 78 | /// Since 3.10.0 79 | /// 80 | public bool foldingRangeProvider { get; set; } // boolean | FoldingRangeProviderOptions | (FoldingRangeProviderOptions & TextDocumentRegistrationOptions & StaticRegistrationOptions); 81 | 82 | internal class Workspace 83 | { 84 | internal class WorkspaceFolders 85 | { 86 | /// 87 | /// The server has support for workspace folders 88 | /// 89 | public bool supported { get; set; } = true; 90 | 91 | /// 92 | /// Whether the server wants to receive workspace folder 93 | /// change notifications. 94 | /// 95 | /// If a strings is provided the string is treated as a ID 96 | /// under which the notification is registered on the client 97 | /// side.The ID can be used to unregister for these events 98 | /// using the `client/unregisterCapability` request. 99 | /// 100 | public object changeNotifications { get; set; } = true; 101 | } 102 | 103 | [JsonProperty("workspaceFolders")] 104 | public WorkspaceFolders workspaceFolders { get; set; } = new WorkspaceFolders(); 105 | } 106 | 107 | [JsonProperty("workspace")] 108 | public Workspace workspace { get; set; } = new Workspace(); 109 | } 110 | 111 | [JsonObject] 112 | class SignatureHelpOptions 113 | { 114 | [JsonProperty("triggerCharacters")] 115 | public char[] TriggerCharacters { get; set; } 116 | } 117 | 118 | /** 119 | * Format document on type options 120 | */ 121 | [JsonObject] 122 | class DocumentOnTypeFormattingOptions 123 | { 124 | /** 125 | * A character on which formatting should be triggered, like `}`. 126 | */ 127 | [JsonProperty("firstTriggerCharacter")] 128 | public char FirstTriggerCharacter { get; set; } 129 | 130 | /** 131 | * More trigger characters. 132 | */ 133 | [JsonProperty("moreTriggerCharacter")] 134 | public char[] MoreTriggerCharacter { get; set; } 135 | } 136 | 137 | [JsonObject] 138 | class CompletionOptions 139 | { 140 | /// 141 | /// The server provides support to resolve additional information for a completion item. 142 | /// 143 | [JsonProperty("resolveProvider")] 144 | public bool ResolveProvider { get; set; } 145 | 146 | /// 147 | /// The characters that trigger completion automatically. 148 | /// 149 | [JsonProperty("triggerCharacters")] 150 | public char[] TriggerCharacters { get; set; } 151 | } 152 | 153 | enum TextDocumentSyncKind 154 | { 155 | /** 156 | * Documents should not be synced at all. 157 | */ 158 | None = 0, 159 | 160 | /** 161 | * Documents are synced by always sending the full content 162 | * of the document. 163 | */ 164 | Full = 1, 165 | 166 | /** 167 | * Documents are synced by sending the full content on open. 168 | * After that only incremental updates to the document are 169 | * send. 170 | */ 171 | Incremental = 2, 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/Peachpie.LanguageServer/Protocol/ShowMessageParams.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace Peachpie.LanguageServer.Protocol 8 | { 9 | [JsonObject] 10 | internal class ShowMessageParams 11 | { 12 | [JsonProperty("type")] 13 | public int Type { get; set; } 14 | 15 | [JsonProperty("message")] 16 | public string Message { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Peachpie.LanguageServer/Protocol/TextDocumentContentChangeEvent.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace Peachpie.LanguageServer.Protocol 8 | { 9 | // TODO: Add also range and rangeLength for the incremental approach 10 | [JsonObject] 11 | internal class TextDocumentContentChangeEvent 12 | { 13 | [JsonProperty("text")] 14 | public string Text { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Peachpie.LanguageServer/Protocol/TextDocumentIdentifier.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace Peachpie.LanguageServer.Protocol 8 | { 9 | [JsonObject] 10 | internal class TextDocumentIdentifier 11 | { 12 | [JsonProperty("uri")] 13 | public string Uri { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Peachpie.LanguageServer/Protocol/TextDocumentItem.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace Peachpie.LanguageServer.Protocol 8 | { 9 | [JsonObject] 10 | internal class TextDocumentItem 11 | { 12 | [JsonProperty("uri")] 13 | public string Uri { get; set; } 14 | 15 | [JsonProperty("languageId")] 16 | public string LanguageId { get; set; } 17 | 18 | [JsonProperty("version")] 19 | public int Version { get; set; } 20 | 21 | [JsonProperty("text")] 22 | public string Text { get; set; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Peachpie.LanguageServer/Protocol/TextDocumentPositionParams.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace Peachpie.LanguageServer.Protocol 7 | { 8 | [JsonObject] 9 | internal class TextDocumentPositionParams 10 | { 11 | [JsonProperty("textDocument")] 12 | public TextDocumentIdentifier TextDocument { get; set; } 13 | 14 | [JsonProperty("position")] 15 | public Position Position { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Peachpie.LanguageServer/Protocol/VersionedTextDocumentIdentifier.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace Peachpie.LanguageServer.Protocol 8 | { 9 | [JsonObject] 10 | internal class VersionedTextDocumentIdentifier : TextDocumentIdentifier 11 | { 12 | [JsonProperty("version")] 13 | public int Version { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Peachpie.LanguageServer/ServerOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace Peachpie.LanguageServer 5 | { 6 | internal class ServerOptions 7 | { 8 | public bool IsDebug { get; } 9 | 10 | public ServerOptions(bool isDebug) 11 | { 12 | this.IsDebug = isDebug; 13 | } 14 | 15 | public static ServerOptions ParseFromArguments(string[] args) 16 | { 17 | bool isDebug = args.Contains("--debug"); 18 | 19 | return new ServerOptions(isDebug); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/Peachpie.LanguageServer/SourceSymbolSearcher.cs: -------------------------------------------------------------------------------- 1 | using Devsense.PHP.Syntax.Ast; 2 | using Devsense.PHP.Text; 3 | using Microsoft.CodeAnalysis; 4 | using Microsoft.CodeAnalysis.Operations; 5 | using Pchp.CodeAnalysis; 6 | using Pchp.CodeAnalysis.FlowAnalysis; 7 | using Pchp.CodeAnalysis.Semantics; 8 | using Pchp.CodeAnalysis.Semantics.Graph; 9 | using Pchp.CodeAnalysis.Symbols; 10 | using Peachpie.CodeAnalysis.Utilities; 11 | using System; 12 | using System.Collections.Generic; 13 | using System.Linq; 14 | using System.Text; 15 | 16 | namespace Peachpie.LanguageServer 17 | { 18 | internal class SourceSymbolSearcher : GraphExplorer 19 | { 20 | public class SymbolStat 21 | { 22 | public Span Span { get; set; } 23 | public IPhpExpression BoundExpression { get; set; } 24 | public ISymbol Symbol { get; set; } 25 | public TypeRefContext TypeCtx { get; } 26 | 27 | public SymbolStat(TypeRefContext tctx, Span span, IPhpExpression expr = null, ISymbol symbol = null) 28 | { 29 | this.TypeCtx = tctx; 30 | this.Span = span; 31 | this.BoundExpression = expr; 32 | this.Symbol = symbol; 33 | } 34 | } 35 | 36 | private int _position; 37 | 38 | private TypeRefContext _tctx; 39 | private SymbolStat _result; 40 | 41 | PhpCompilation DeclaringCompilation { get; set; } 42 | 43 | private SourceSymbolSearcher(int position) 44 | { 45 | _position = position; 46 | } 47 | 48 | public static SymbolStat SearchCFG(PhpCompilation compilation, ControlFlowGraph cfg, int position) 49 | { 50 | var visitor = new SourceSymbolSearcher(position) 51 | { 52 | DeclaringCompilation = compilation, 53 | }; 54 | visitor.VisitCFG(cfg); 55 | return visitor._result; 56 | } 57 | 58 | public static SymbolStat SearchParameters(IPhpRoutineSymbol routine, int position) 59 | { 60 | var parameter = routine.Parameters.FirstOrDefault(p => p.GetSpan().Contains(position)); 61 | if (parameter != null) 62 | { 63 | TypeRefContext typeCtx = routine.ControlFlowGraph?.FlowContext?.TypeRefContext; 64 | return new SymbolStat(typeCtx, parameter.GetSpan(), null, parameter); 65 | } 66 | else 67 | { 68 | return null; 69 | } 70 | } 71 | 72 | public static SymbolStat SearchMembers(IPhpTypeSymbol type, int position) 73 | { 74 | return null; 75 | } 76 | 77 | protected override void VisitCFGInternal(ControlFlowGraph x) 78 | { 79 | _tctx = x.FlowContext?.TypeRefContext; 80 | base.VisitCFGInternal(x); 81 | } 82 | 83 | protected override void DefaultVisitUnexploredBlock(BoundBlock x) 84 | { 85 | if (_result == null) 86 | { 87 | base.DefaultVisitUnexploredBlock(x); 88 | } 89 | } 90 | 91 | public override VoidStruct VisitVariableRef(BoundVariableRef x) 92 | { 93 | if (x.PhpSyntax?.Span.Contains(_position) == true) 94 | { 95 | var symbolOpt = x is ILocalReferenceOperation loc ? loc.Local : null; 96 | 97 | _result = new SymbolStat(_tctx, x.PhpSyntax.Span, x, symbolOpt); 98 | } 99 | 100 | // 101 | return base.VisitVariableRef(x); 102 | } 103 | 104 | protected override VoidStruct DefaultVisitOperation(BoundOperation x) 105 | { 106 | if (x is IBoundTypeRef tref && tref.PhpSyntax != null && tref.PhpSyntax.Span.Contains(_position)) 107 | { 108 | var symbol = tref.ResolveTypeSymbol(DeclaringCompilation); 109 | if (symbol != null) 110 | { 111 | _result = new SymbolStat(_tctx, tref.PhpSyntax.Span, null, symbol); 112 | } 113 | } 114 | 115 | return base.DefaultVisitOperation(x); 116 | } 117 | 118 | protected override VoidStruct VisitRoutineCall(BoundRoutineCall x) 119 | { 120 | if (x.PhpSyntax != null && x.PhpSyntax.Span.Contains(_position) == true) 121 | { 122 | var invocation = (IInvocationOperation)x; 123 | if (invocation.TargetMethod != null) 124 | { 125 | if (!invocation.TargetMethod.IsImplicitlyDeclared || invocation.TargetMethod is IErrorMethodSymbol) 126 | { 127 | Span span; 128 | if (x.PhpSyntax is FunctionCall) 129 | { 130 | span = ((FunctionCall)x.PhpSyntax).NameSpan; 131 | if (span.Contains(_position)) 132 | { 133 | _result = new SymbolStat(_tctx, span, x, invocation.TargetMethod); 134 | } 135 | } 136 | } 137 | } 138 | } 139 | 140 | // 141 | return base.VisitRoutineCall(x); 142 | } 143 | 144 | public override VoidStruct VisitGlobalConstUse(BoundGlobalConst x) 145 | { 146 | if (x.PhpSyntax?.Span.Contains(_position) == true) 147 | { 148 | _result = new SymbolStat(_tctx, x.PhpSyntax.Span, x, null); 149 | } 150 | 151 | // 152 | return base.VisitGlobalConstUse(x); 153 | } 154 | 155 | public override VoidStruct VisitPseudoConstUse(BoundPseudoConst x) 156 | { 157 | if (x.PhpSyntax?.Span.Contains(_position) == true) 158 | { 159 | _result = new SymbolStat(_tctx, x.PhpSyntax.Span, x, null); 160 | } 161 | 162 | return base.VisitPseudoConstUse(x); 163 | } 164 | 165 | public override VoidStruct VisitFieldRef(BoundFieldRef x) 166 | { 167 | if (x.PhpSyntax?.Span.Contains(_position) == true) 168 | { 169 | Span span = x.PhpSyntax.Span; 170 | 171 | //if (x.PhpSyntax is StaticFieldUse) 172 | //{ 173 | // span = ((StaticFieldUse)x.PhpSyntax).NameSpan; 174 | //} 175 | //else if (x.PhpSyntax is ClassConstUse) 176 | //{ 177 | // span = ((ClassConstUse)x.PhpSyntax).NamePosition; 178 | //} 179 | 180 | if (span.IsValid) 181 | { 182 | _result = new SymbolStat(_tctx, span, x, ((IMemberReferenceOperation)x).Member); 183 | } 184 | } 185 | 186 | // 187 | return base.VisitFieldRef(x); 188 | } 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/Peachpie.LanguageServer/StrongKeys/core.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iolevel/peachpie-vscode/735f07480714946a8fe90184c86e5275feca881f/src/Peachpie.LanguageServer/StrongKeys/core.snk -------------------------------------------------------------------------------- /src/Peachpie.LanguageServer/ToolTipInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Peachpie.LanguageServer 6 | { 7 | public class ToolTipInfo 8 | { 9 | public string Code { get; } 10 | 11 | public string Description { get; } 12 | 13 | public ToolTipInfo(string code, string description = null) 14 | { 15 | this.Code = code; 16 | this.Description = description; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Peachpie.LanguageServer/Utils/ILogTarget.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Peachpie.LanguageServer.Utils 6 | { 7 | interface ILogTarget 8 | { 9 | void LogMessage(string message); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Peachpie.LanguageServer/Utils/PathUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace Peachpie.LanguageServer 7 | { 8 | public static class PathUtils 9 | { 10 | public static string NormalizePath(string path) 11 | { 12 | if (path.StartsWith("file:///")) 13 | { 14 | var uri = new Uri(path); 15 | path = Uri.UnescapeDataString(uri.AbsolutePath); 16 | 17 | // Fix /c:/... 18 | if (path.Length >= 3 && path[0] == '/' && path[2] == ':') 19 | { 20 | path = path.Substring(1); 21 | } 22 | } 23 | 24 | path = path.Replace('\\', '/'); 25 | 26 | if (path.EndsWith("/")) 27 | { 28 | path = path.Substring(0, path.Length - 1); 29 | } 30 | 31 | return path; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Peachpie.LanguageServer/Utils/ProjectUtils.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Build.Construction; 2 | using Microsoft.Build.Evaluation; 3 | using Microsoft.CodeAnalysis; 4 | using Pchp.CodeAnalysis; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Collections.Immutable; 8 | using System.IO; 9 | using System.Linq; 10 | using System.Threading.Tasks; 11 | using System.Xml; 12 | using Microsoft.Build.Execution; 13 | using Microsoft.Build.Framework; 14 | using System.Threading; 15 | using Microsoft.Build.Logging; 16 | using Microsoft.CodeAnalysis.Text; 17 | using System.Text; 18 | using Peachpie.LanguageServer.Workspaces; 19 | using System.Globalization; 20 | using Peachpie.LanguageServer.Utils; 21 | using System.Diagnostics; 22 | using Microsoft.Build.Utilities; 23 | 24 | namespace Peachpie.LanguageServer 25 | { 26 | static class ProjectUtils 27 | { 28 | private const string SolutionNamePattern = "*.sln"; 29 | private const string ProjectNamePattern = "*.msbuildproj"; 30 | 31 | private const string ToolsVersion = "Current"; 32 | private static readonly XmlReaderSettings XmlSettings = new XmlReaderSettings() 33 | { 34 | DtdProcessing = DtdProcessing.Prohibit 35 | }; 36 | 37 | private const string LogFileName = "build.log"; 38 | 39 | private const string HelperReferenceReturnTarget = "ReturnReferences"; 40 | 41 | private static SemaphoreSlim _buildManagerSemaphore = new SemaphoreSlim(1); 42 | 43 | public static async Task TryGetFirstPhpProjectAsync(string directory, ILogTarget log) 44 | { 45 | foreach (var solutionPath in Directory.GetFiles(directory, SolutionNamePattern)) 46 | { 47 | var solution = TryLoadSolution(solutionPath); 48 | if (solution != null) 49 | { 50 | foreach (var project in solution.ProjectsInOrder) 51 | { 52 | if (project.ProjectType != SolutionProjectType.KnownToBeMSBuildFormat || 53 | project.RelativePath.EndsWith(".csproj")) 54 | { 55 | continue; 56 | } 57 | 58 | var projectHandler = await TryGetPhpProjectAsync(project.AbsolutePath, log); 59 | if (projectHandler != null) 60 | { 61 | return projectHandler; 62 | } 63 | } 64 | } 65 | } 66 | 67 | foreach (var projectPath in Directory.GetFiles(directory, ProjectNamePattern, SearchOption.AllDirectories)) 68 | { 69 | var projectHandler = await TryGetPhpProjectAsync(projectPath, log); 70 | if (projectHandler != null) 71 | { 72 | return projectHandler; 73 | } 74 | } 75 | 76 | return null; 77 | } 78 | 79 | private static async Task TryGetPhpProjectAsync(string projectFile, ILogTarget log) 80 | { 81 | try 82 | { 83 | Project project = LoadProject(projectFile); 84 | 85 | if (!IsPhpProject(project)) 86 | { 87 | return null; 88 | } 89 | 90 | SetupMultitargetingIfNecessary(project); 91 | 92 | var buildResult = await ResolveReferencesAsync(project); 93 | var projectInstance = buildResult.ProjectStateAfterBuild; 94 | 95 | var metadataReferences = GatherReferences(project, projectInstance, buildResult) 96 | .Select(path => MetadataReference.CreateFromFile(path, documentation: XmlDocumentationProvider.CreateFromFile(Path.ChangeExtension(path, "xml")))) 97 | .ToArray(); 98 | 99 | if (metadataReferences.Length == 0) 100 | { 101 | // dotnet restore hasn't run yet 102 | log?.LogMessage($"{Path.GetFileName(projectFile)}: could not be built; "); 103 | return null; 104 | } 105 | 106 | var options = new PhpCompilationOptions( 107 | outputKind: OutputKind.DynamicallyLinkedLibrary, 108 | baseDirectory: Path.GetDirectoryName(projectFile), // compilation expects platform-specific slashes (backslashes on windows) 109 | specificDiagnosticOptions: null, 110 | sdkDirectory: null); 111 | 112 | // TODO: Get from MSBuild 113 | string projectName = Path.GetFileNameWithoutExtension(projectFile); 114 | 115 | var encoding = TryParseEncodingName(projectInstance.GetPropertyValue("CodePage")) ?? Encoding.UTF8; 116 | 117 | var nowarn = projectInstance.GetPropertyValue("NoWarn"); 118 | if (nowarn != null) 119 | { 120 | options = options.WithSpecificDiagnosticOptions(ParseNoWarn(nowarn)); 121 | } 122 | 123 | var syntaxTrees = ParseSourceFiles(projectInstance, encoding); 124 | 125 | var compilation = PhpCompilation.Create( 126 | projectName, 127 | syntaxTrees, 128 | references: metadataReferences, 129 | options: options); 130 | 131 | return new ProjectHandler(compilation, projectInstance, encoding); 132 | } 133 | catch (Exception ex) 134 | { 135 | log?.LogMessage($"{Path.GetFileName(projectFile)}: {ex.Message}"); 136 | return null; 137 | } 138 | } 139 | 140 | private static IEnumerable> ParseNoWarn(string nowarn) 141 | { 142 | if (!string.IsNullOrEmpty(nowarn)) 143 | { 144 | foreach (var warn in nowarn.Split(new char[] { ',', ';', ' ' }, StringSplitOptions.RemoveEmptyEntries)) 145 | { 146 | yield return new KeyValuePair(warn.Trim(), ReportDiagnostic.Hidden); 147 | } 148 | } 149 | } 150 | 151 | private static SolutionFile TryLoadSolution(string solutionPath) 152 | { 153 | try 154 | { 155 | return SolutionFile.Parse(solutionPath); 156 | } 157 | catch (Exception) 158 | { 159 | return null; 160 | } 161 | } 162 | 163 | private static Project LoadProject(string projectFile) 164 | { 165 | var toolsPath = EnvironmentUtils.NetCoreRuntimePath; 166 | var globalProperties = EnvironmentUtils.GetCoreGlobalProperties(projectFile, toolsPath); 167 | var projectCollection = new ProjectCollection(globalProperties); 168 | 169 | projectCollection.AddToolset(new Toolset(ToolLocationHelper.CurrentToolsVersion, toolsPath, projectCollection, string.Empty)); 170 | 171 | Environment.SetEnvironmentVariable("MSBuildExtensionsPath", EnvironmentUtils.NetCoreRuntimePath); 172 | Environment.SetEnvironmentVariable("MSBuildSDKsPath", EnvironmentUtils.MSBuildSDKsPath); 173 | Environment.SetEnvironmentVariable("MSBuildEnableWorkloadResolver", bool.FalseString); // NET 6.0 + 174 | 175 | // TODO: Make properly async 176 | var fileContents = new StringReader(File.ReadAllText(projectFile)); // read {projectFile} separately in order to avoid locking it on FS 177 | var xmlReader = XmlReader.Create(fileContents, XmlSettings); 178 | var projectRoot = ProjectRootElement.Create(xmlReader, projectCollection); 179 | 180 | // In order to have it accessible from MSBuild 181 | projectRoot.FullPath = projectFile; 182 | 183 | var properties = new Dictionary() 184 | { 185 | // In order not to build the dependent projects 186 | { "DesignTimeBuild", "true" }, 187 | }; 188 | 189 | //Project project = projectCollection.LoadProject(projectPath); 190 | return new Project(projectRoot, properties, toolsVersion: null, projectCollection: projectCollection); 191 | } 192 | 193 | private static bool IsPeachPieCompilerImport(ResolvedImport import) 194 | { 195 | return import 196 | .ImportedProject 197 | .FullPath 198 | .IndexOf("peachpie.net.sdk", StringComparison.OrdinalIgnoreCase) >= 0; 199 | } 200 | 201 | private static bool IsPhpProject(Project project) 202 | { 203 | return project.Imports.Any(IsPeachPieCompilerImport); 204 | } 205 | 206 | private static bool IsMultitargetingProject(Project project) 207 | { 208 | return project.GetPropertyValue("IsCrossTargetingBuild") == "true"; 209 | } 210 | 211 | private static void SetupMultitargetingIfNecessary(Project project) 212 | { 213 | if (IsMultitargetingProject(project)) 214 | { 215 | // Force DispatchToInnerBuilds target to run a helper target from Peachpie SDK instead of Build 216 | project.SetProperty("InnerTargets", HelperReferenceReturnTarget); 217 | project.ReevaluateIfNecessary(); 218 | } 219 | } 220 | 221 | /// 222 | /// Extends support for encoding names in additon to codepages. 223 | /// 224 | static Encoding TryParseEncodingName(string arg) 225 | { 226 | if (!string.IsNullOrEmpty(arg)) 227 | { 228 | try 229 | { 230 | var encoding = Encoding.GetEncoding(arg); 231 | if (encoding != null) 232 | { 233 | return encoding; 234 | } 235 | } 236 | catch 237 | { 238 | // ignore 239 | } 240 | 241 | // default behavior: 242 | if (long.TryParse(arg, NumberStyles.None, CultureInfo.InvariantCulture, out var codepage) && codepage > 0) 243 | { 244 | try 245 | { 246 | return Encoding.GetEncoding((int)codepage); 247 | } 248 | catch (Exception) 249 | { 250 | } 251 | } 252 | } 253 | 254 | // 255 | return null; 256 | } 257 | 258 | private static async Task ResolveReferencesAsync(Project project) 259 | { 260 | var projectInstance = project.CreateProjectInstance(); 261 | string target = IsMultitargetingProject(project) ? "DispatchToInnerBuilds" : "ResolveReferences"; 262 | var buildRequestData = new BuildRequestData(projectInstance, new string[] { target }); 263 | 264 | var buildManager = BuildManager.DefaultBuildManager; 265 | var buildParameters = new BuildParameters(project.ProjectCollection); 266 | 267 | #if DEBUG 268 | // Log output in debug mode 269 | string logFilePath = Path.Combine( 270 | project.DirectoryPath, 271 | projectInstance.GetPropertyValue("BaseIntermediateOutputPath"), // "obj" subfolder 272 | LogFileName); 273 | 274 | buildParameters.Loggers = new ILogger[] 275 | { 276 | new FileLogger() 277 | { 278 | Verbosity = LoggerVerbosity.Detailed, 279 | Parameters = $"LogFile={logFilePath}" 280 | } 281 | }; 282 | #endif 283 | 284 | await _buildManagerSemaphore.WaitAsync(); 285 | try 286 | { 287 | return await RunBuildAsync(projectInstance, buildRequestData, buildManager, buildParameters); 288 | } 289 | finally 290 | { 291 | _buildManagerSemaphore.Release(); 292 | } 293 | } 294 | 295 | private static Task RunBuildAsync( 296 | ProjectInstance projectInstance, 297 | BuildRequestData buildRequestData, 298 | BuildManager buildManager, 299 | BuildParameters buildParameters) 300 | { 301 | var taskSource = new TaskCompletionSource(); 302 | 303 | buildManager.BeginBuild(buildParameters); 304 | 305 | buildManager.PendBuildRequest(buildRequestData).ExecuteAsync(sub => 306 | { 307 | try 308 | { 309 | buildManager.EndBuild(); 310 | 311 | sub.BuildResult.ProjectStateAfterBuild = projectInstance; 312 | taskSource.TrySetResult(sub.BuildResult); 313 | } 314 | catch (Exception e) 315 | { 316 | taskSource.TrySetException(e); 317 | } 318 | }, null); 319 | 320 | return taskSource.Task; 321 | } 322 | 323 | private static IEnumerable GatherReferences( 324 | Project project, 325 | ProjectInstance projectInstance, 326 | BuildResult buildResult) 327 | { 328 | if (IsMultitargetingProject(project)) 329 | { 330 | // Perform analysis always only on the first listed framework 331 | string frameworks = project.GetPropertyValue("TargetFrameworks"); 332 | string firstFramework = frameworks.Split(';')[0]; 333 | 334 | // Filter all the resulting references from the DispatchToInnerBuilds target by framework 335 | return buildResult.ResultsByTarget.First().Value.Items 336 | .Where(item => item.GetMetadata("TargetFramework") == firstFramework) 337 | .Select(item => item.ItemSpec); 338 | } 339 | else 340 | { 341 | return projectInstance.GetItems("ReferencePath") 342 | .Select(item => item.EvaluatedInclude); 343 | } 344 | } 345 | 346 | private static PhpSyntaxTree[] ParseSourceFiles(ProjectInstance projectInstance, Encoding encoding) 347 | { 348 | var sourceFiles = projectInstance 349 | .GetItems("Compile") 350 | .Select(x => Path.Combine(projectInstance.Directory, x.EvaluatedInclude)).ToArray(); 351 | 352 | var syntaxTrees = new PhpSyntaxTree[sourceFiles.Length]; 353 | 354 | Parallel.For(0, sourceFiles.Length, i => 355 | { 356 | var path = PathUtils.NormalizePath(sourceFiles[i]); 357 | if (path.EndsWith(".phar")) 358 | { 359 | // TODO: process phar archives 360 | syntaxTrees[i] = PhpSyntaxTree.ParseCode(SourceText.From(string.Empty), PhpParseOptions.Default, PhpParseOptions.Default, path); 361 | } 362 | else 363 | { 364 | syntaxTrees[i] = PhpSyntaxTree.ParseCode(SourceText.From(File.OpenRead(path), encoding), PhpParseOptions.Default, PhpParseOptions.Default, path); 365 | } 366 | }); 367 | 368 | return syntaxTrees; 369 | } 370 | } 371 | } 372 | -------------------------------------------------------------------------------- /src/Peachpie.LanguageServer/Utils/SpanUtils.cs: -------------------------------------------------------------------------------- 1 | using Devsense.PHP.Text; 2 | using Microsoft.CodeAnalysis; 3 | using Pchp.CodeAnalysis.Symbols; 4 | using Peachpie.LanguageServer.Protocol; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Text; 9 | 10 | namespace Peachpie.LanguageServer 11 | { 12 | static class SpanUtils 13 | { 14 | public static Span ToSpan(this Microsoft.CodeAnalysis.Text.TextSpan span) 15 | { 16 | return new Span(span.Start, span.Length); 17 | } 18 | 19 | public static Microsoft.CodeAnalysis.Text.TextSpan ToSpan(this Span span) 20 | { 21 | return new Microsoft.CodeAnalysis.Text.TextSpan(span.Start, span.Length); 22 | } 23 | 24 | public static Span GetSpan(this IPhpRoutineSymbol routine) 25 | { 26 | return routine.Locations.FirstOrDefault(l => l.IsInSource)?.SourceSpan.ToSpan() ?? Span.Invalid; 27 | } 28 | 29 | public static Span GetSpan(this IParameterSymbol parameter) 30 | { 31 | return parameter.IsImplicitlyDeclared ? Span.Invalid : 32 | parameter.Locations.FirstOrDefault(l => l.IsInSource)?.SourceSpan.ToSpan() ?? Span.Invalid; 33 | } 34 | 35 | public static Protocol.Range AsRange(this Microsoft.CodeAnalysis.Location location) => AsRange(location.GetLineSpan()); 36 | 37 | public static Protocol.Range AsRange(this FileLinePositionSpan span) 38 | { 39 | return new Protocol.Range( 40 | new Position(span.StartLinePosition.Line, span.StartLinePosition.Character), 41 | new Position(span.EndLinePosition.Line, span.EndLinePosition.Character)); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Peachpie.LanguageServer/Utils/ToolTipUtils.cs: -------------------------------------------------------------------------------- 1 | using Devsense.PHP.Syntax; 2 | using Microsoft.CodeAnalysis; 3 | using Microsoft.CodeAnalysis.Operations; 4 | using Microsoft.CodeAnalysis.Text; 5 | using Pchp.CodeAnalysis; 6 | using Pchp.CodeAnalysis.FlowAnalysis; 7 | using Pchp.CodeAnalysis.Semantics; 8 | using Pchp.CodeAnalysis.Symbols; 9 | using System; 10 | using System.Collections.Generic; 11 | using System.Collections.Immutable; 12 | using System.Globalization; 13 | using System.IO; 14 | using System.Linq; 15 | using System.Text; 16 | using System.Text.RegularExpressions; 17 | using System.Xml; 18 | 19 | namespace Peachpie.LanguageServer 20 | { 21 | internal static class ToolTipUtils 22 | { 23 | public static SourceSymbolSearcher.SymbolStat FindDefinition(PhpCompilation compilation, string filepath, int line, int character) 24 | { 25 | var tree = compilation.SyntaxTrees.FirstOrDefault(t => t.FilePath == filepath); 26 | if (tree == null) 27 | { 28 | return null; 29 | } 30 | 31 | // Get the position in the file 32 | int position = tree.GetOffset(new LinePosition(line, character)); 33 | if (position == -1) 34 | { 35 | return null; 36 | } 37 | 38 | // Find the bound node corresponding to the text position 39 | SourceSymbolSearcher.SymbolStat searchResult = null; 40 | foreach (var routine in compilation.GetUserDeclaredRoutinesInFile(tree)) 41 | { 42 | // Consider only routines containing the position being searched (
has span [0..0]) 43 | if (routine.IsGlobalScope || routine.GetSpan().Contains(position)) 44 | { 45 | // Search parameters at first 46 | searchResult = SourceSymbolSearcher.SearchParameters(routine, position); 47 | if (searchResult != null) 48 | { 49 | break; 50 | } 51 | 52 | // Search the routine body 53 | searchResult = SourceSymbolSearcher.SearchCFG(compilation, routine.ControlFlowGraph, position); 54 | if (searchResult != null) 55 | { 56 | break; 57 | } 58 | } 59 | } 60 | 61 | return searchResult; 62 | } 63 | 64 | public static IEnumerable ObtainDefinition(PhpCompilation compilation, string filepath, int line, int character) 65 | { 66 | var result = FindDefinition(compilation, filepath, line, character); 67 | if (result != null && result.Symbol != null) 68 | { 69 | ImmutableArray location; 70 | 71 | try 72 | { 73 | location = result.Symbol.Locations; 74 | } 75 | catch 76 | { 77 | yield break; // location not impl. 78 | } 79 | 80 | foreach (var loc in location) 81 | { 82 | var pos = loc.GetLineSpan(); 83 | if (pos.IsValid) 84 | { 85 | yield return new Protocol.Location 86 | { 87 | Uri = new Uri(pos.Path).AbsoluteUri, 88 | Range = pos.AsRange(), 89 | }; 90 | } 91 | } 92 | } 93 | } 94 | 95 | /// 96 | /// Returns the text of a tooltip corresponding to the given position in the code. 97 | /// 98 | public static ToolTipInfo ObtainToolTip(PhpCompilation compilation, string filepath, int line, int character) 99 | { 100 | return FormulateToolTip(FindDefinition(compilation, filepath, line, character)); 101 | } 102 | 103 | static string FormulateTypeHint(ITypeSymbol type) 104 | { 105 | if (type == null) 106 | { 107 | return null; 108 | } 109 | 110 | return type.SpecialType switch 111 | { 112 | SpecialType.System_Int32 => "int", 113 | SpecialType.System_Int64 => "int", 114 | SpecialType.System_Double => "float", 115 | SpecialType.System_Boolean => "boolean", 116 | SpecialType.System_String => "string", 117 | SpecialType.System_Object => "object", 118 | _ => type.MetadataName switch 119 | { 120 | "PhpValue" => null, 121 | "PhpAlias" => "&", 122 | "PhpResource" => "resource", 123 | "PhpArray" => "array", 124 | "PhpString" => "string", 125 | // TODO: byte[] 126 | _ => type.Name, 127 | } 128 | }; 129 | } 130 | 131 | /// 132 | /// Creates the text of a tooltip. 133 | /// 134 | private static ToolTipInfo FormulateToolTip(SourceSymbolSearcher.SymbolStat searchResult) 135 | { 136 | if (searchResult == null || (searchResult.Symbol == null && searchResult.BoundExpression == null)) 137 | { 138 | return null; 139 | } 140 | 141 | var ctx = searchResult.TypeCtx; 142 | var expression = searchResult.BoundExpression; 143 | var symbol = searchResult.Symbol; 144 | if (symbol is IErrorMethodSymbol errSymbol) 145 | { 146 | if (errSymbol.ErrorKind == ErrorMethodKind.Missing) 147 | { 148 | return null; 149 | } 150 | 151 | symbol = errSymbol.OriginalSymbols.LastOrDefault(); 152 | } 153 | 154 | var result = new StringBuilder(32); 155 | var kind = string.Empty; 156 | 157 | if (expression is BoundVariableRef varref && varref.Name.IsDirect) 158 | { 159 | var name = varref.Name.NameValue; 160 | 161 | if (symbol is IParameterSymbol) 162 | { 163 | kind = "parameter"; 164 | } 165 | else 166 | { 167 | kind = "variable"; 168 | } 169 | 170 | //switch ((((BoundVariableRef)expression).Variable).VariableKind) 171 | //{ 172 | // case VariableKind.LocalVariable: 173 | // result.Append("(var) "); break; 174 | // case VariableKind.Parameter: 175 | // result.Append("(parameter) "); break; 176 | // case VariableKind.GlobalVariable: 177 | // result.Append("(global) "); break; 178 | // case VariableKind.StaticVariable: 179 | // result.Append("(static local) "); break; 180 | //} 181 | 182 | result.Append("$" + name); 183 | 184 | } 185 | else if (expression is BoundGlobalConst) 186 | { 187 | kind = "global constant"; 188 | result.Append(((BoundGlobalConst)expression).Name); 189 | } 190 | else if (expression is BoundPseudoConst) 191 | { 192 | kind = "magic constant"; 193 | result.Append("__"); 194 | result.Append(((BoundPseudoConst)expression).ConstType.ToString().ToUpperInvariant()); 195 | result.Append("__"); 196 | } 197 | else if (symbol is IParameterSymbol) 198 | { 199 | kind = "parameter"; 200 | result.Append("$" + symbol.Name); 201 | } 202 | else if (symbol is IPhpRoutineSymbol) 203 | { 204 | var routine = (IPhpRoutineSymbol)symbol; 205 | result.Append("function "); 206 | result.Append(routine.RoutineName); 207 | result.Append('('); 208 | int nopt = 0; 209 | bool first = true; 210 | foreach (var p in routine.Parameters) 211 | { 212 | if (p.IsImplicitlyDeclared) continue; 213 | if (p.IsOptional) 214 | { 215 | nopt++; 216 | result.Append('['); 217 | } 218 | if (first) first = false; 219 | else result.Append(", "); 220 | 221 | var thint = FormulateTypeHint(p.Type); 222 | if (thint != null) 223 | { 224 | result.Append(thint + " "); 225 | } 226 | result.Append("$" + p.Name); 227 | } 228 | while (nopt-- > 0) { result.Append(']'); } 229 | result.Append(')'); 230 | } 231 | else if (expression is BoundFieldRef fld) 232 | { 233 | if (fld.FieldName.IsDirect) 234 | { 235 | string containedType = null; 236 | 237 | if (fld.IsClassConstant) 238 | { 239 | kind = "class constant"; 240 | } 241 | else 242 | { 243 | result.Append(fld.IsStaticField ? "static" : "var"); 244 | }; 245 | 246 | if (fld.Instance != null) 247 | { 248 | //if (fld.Instance.TypeRefMask.IsAnyType || fld.Instance.TypeRefMask.IsVoid) return null; 249 | 250 | containedType = ctx.ToString(fld.Instance.TypeRefMask); 251 | } 252 | else 253 | { 254 | containedType = fld.ContainingType?.ToString(); 255 | } 256 | 257 | result.Append(' '); 258 | if (containedType != null) 259 | { 260 | result.Append(containedType); 261 | result.Append(Name.ClassMemberSeparator); 262 | } 263 | if (!fld.IsClassConstant) 264 | { 265 | result.Append("$"); 266 | } 267 | result.Append(fld.FieldName.NameValue.Value); 268 | } 269 | else 270 | { 271 | return null; 272 | } 273 | } 274 | else if (symbol is IPhpTypeSymbol phpt) 275 | { 276 | kind = "type"; 277 | if (phpt.TypeKind == TypeKind.Interface) result.Append("interface"); 278 | else if (phpt.IsTrait) result.Append("trait"); 279 | else result.Append("class"); 280 | 281 | result.Append(' '); 282 | result.Append(phpt.FullName.ToString()); 283 | } 284 | else 285 | { 286 | return null; 287 | } 288 | 289 | // : type 290 | if (ctx != null) 291 | { 292 | TypeRefMask? mask = null; 293 | 294 | if (expression != null) 295 | { 296 | mask = expression.TypeRefMask; 297 | } 298 | else if (symbol is IPhpValue resultVal) 299 | { 300 | mask = resultVal.GetResultType(ctx); 301 | } 302 | 303 | if (mask.HasValue) 304 | { 305 | result.Append(": "); 306 | result.Append(ctx.ToString(mask.Value)); 307 | } 308 | } 309 | 310 | // constant value 311 | if (expression != null && expression.ConstantValue.HasValue) 312 | { 313 | // = 314 | var value = expression.ConstantValue.Value; 315 | string valueStr; 316 | if (value == null) valueStr = "NULL"; 317 | else if (value is int) valueStr = ((int)value).ToString(); 318 | else if (value is string) valueStr = "\"" + ((string)value).Replace("\"", "\\\"").Replace("\n", "\\n") + "\""; 319 | else if (value is long) valueStr = ((long)value).ToString(); 320 | else if (value is double) valueStr = ((double)value).ToString(CultureInfo.InvariantCulture); 321 | else if (value is bool) valueStr = (bool)value ? "TRUE" : "FALSE"; 322 | else valueStr = value.ToString(); 323 | 324 | if (valueStr != null) 325 | { 326 | result.Append(" = "); 327 | result.Append(valueStr); 328 | } 329 | } 330 | 331 | string description; 332 | 333 | try 334 | { 335 | description = XmlDocumentationToMarkdown(symbol?.GetDocumentationCommentXml()); 336 | } 337 | catch 338 | { 339 | description = null; 340 | } 341 | 342 | // description 343 | return new ToolTipInfo("" + xmldoc + ""), settings)) 387 | { 388 | bool skipped = false; 389 | while (skipped || xml.Read()) 390 | { 391 | skipped = false; 392 | 393 | switch (xml.NodeType) 394 | { 395 | case XmlNodeType.Element: 396 | // 397 | switch (xml.Name.ToLowerInvariant()) 398 | { 399 | case "summary": 400 | break; // OK 401 | case "remarks": 402 | result.Append("\n\n**Remarks:**\n"); 403 | break; 404 | case "para": 405 | case "p": 406 | case "br": 407 | result.Append("\n\n"); 408 | break; // continue 409 | case "returns": 410 | result.Append("\n\n**Returns:**\n"); 411 | break; 412 | case "c": 413 | result.AppendFormat("**{0}**", xml.ReadInnerXml()); 414 | skipped = true; 415 | break; 416 | case "paramref": 417 | if (xml.HasAttributes) 418 | result.AppendFormat("**${0}**", xml.GetAttribute("name")); 419 | break; 420 | case "code": 421 | result.AppendFormat("```\n{0}```\n", xml.ReadInnerXml()); 422 | skipped = true; 423 | break; 424 | case "see": 425 | if (xml.HasAttributes) 426 | result.AppendFormat("**{0}**", xml.GetAttribute("langword") ?? CrefToString(xml.GetAttribute("cref"))); 427 | break; 428 | case "a": 429 | if (xml.HasAttributes) 430 | { 431 | result.AppendFormat("({1})[{0}]", xml.GetAttribute("href"), xml.ReadInnerXml()); 432 | skipped = true; 433 | } 434 | break; 435 | // TODO: table, thead, tr, td 436 | default: 437 | xml.Skip(); 438 | skipped = true; // do not call Read()! 439 | break; 440 | } 441 | break; 442 | 443 | case XmlNodeType.Text: 444 | result.Append(xml.Value.Replace("*", "\\*")); 445 | break; 446 | } 447 | } 448 | } 449 | 450 | // 451 | return result.ToString().Trim(); 452 | } 453 | 454 | static string CrefToString(string cref) 455 | { 456 | if (string.IsNullOrEmpty(cref)) 457 | { 458 | return string.Empty; 459 | } 460 | 461 | int trim = Math.Max(cref.LastIndexOf(':'), cref.LastIndexOf('.')); 462 | return trim > 0 463 | ? cref.Substring(trim + 1) 464 | : cref; 465 | } 466 | } 467 | } 468 | -------------------------------------------------------------------------------- /src/Peachpie.LanguageServer/Workspaces/XmlDocumentationProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Build.Tasks; 2 | using Microsoft.CodeAnalysis; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Globalization; 6 | using System.IO; 7 | using System.Text; 8 | using System.Threading; 9 | using System.Xml.Linq; 10 | using System.Xml; 11 | using System.Diagnostics; 12 | using System.Text.RegularExpressions; 13 | 14 | namespace Peachpie.LanguageServer.Workspaces 15 | { 16 | /// 17 | /// A class used to provide XML documentation to the compiler for members from metadata from an XML document source. 18 | /// 19 | public abstract class XmlDocumentationProvider : DocumentationProvider 20 | { 21 | private Dictionary _docComments; 22 | 23 | /// 24 | /// Gets the source stream for the XML document. 25 | /// 26 | /// The cancellation token. 27 | /// 28 | protected abstract Stream GetSourceStream(CancellationToken cancellationToken); 29 | 30 | ///// 31 | ///// Creates an from bytes representing XML documentation data. 32 | ///// 33 | ///// The XML document bytes. 34 | ///// An . 35 | //public static XmlDocumentationProvider CreateFromBytes(byte[] xmlDocCommentBytes) 36 | //{ 37 | // return new ContentBasedXmlDocumentationProvider(xmlDocCommentBytes); 38 | //} 39 | 40 | private static XmlDocumentationProvider DefaultXmlDocumentationProvider { get; } = new NullXmlDocumentationProvider(); 41 | 42 | /// 43 | /// Creates an from an XML documentation file. 44 | /// 45 | /// The path to the XML file. 46 | /// An . 47 | public static XmlDocumentationProvider CreateFromFile(string xmlDocCommentFilePath) 48 | { 49 | if (!File.Exists(xmlDocCommentFilePath)) 50 | { 51 | return DefaultXmlDocumentationProvider; 52 | } 53 | 54 | return new FileBasedXmlDocumentationProvider(xmlDocCommentFilePath); 55 | } 56 | 57 | private XDocument GetXDocument(CancellationToken cancellationToken) 58 | { 59 | using var stream = GetSourceStream(cancellationToken); 60 | using var xmlReader = XmlReader.Create(stream, s_xmlSettings); 61 | return XDocument.Load(xmlReader); 62 | } 63 | 64 | protected override string GetDocumentationForSymbol(string documentationMemberID, CultureInfo preferredCulture, CancellationToken cancellationToken = default) 65 | { 66 | if (_docComments == null) 67 | { 68 | lock (this) 69 | { 70 | if (_docComments == null) 71 | { 72 | _docComments = new Dictionary(); 73 | 74 | try 75 | { 76 | //var doc = GetXDocument(cancellationToken); 77 | //foreach (var e in doc.Descendants("member")) 78 | //{ 79 | // if (e.Attribute("name") != null) 80 | // { 81 | // using var reader = e.CreateReader(); 82 | // reader.MoveToContent(); 83 | // _docComments[e.Attribute("name").Value] = reader.ReadInnerXml(); 84 | // } 85 | //} 86 | 87 | // just cut the elements from XML (there are invalid tags in netstandard.xml so we don't parse XML here) // how do they do it in other IDEs???? 88 | var xml = new StreamReader(GetSourceStream(cancellationToken)).ReadToEnd(); 89 | int from = 0; 90 | for (; ; ) 91 | { 92 | from = xml.IndexOf("", from); 96 | if (to < 0) break; 97 | 98 | var match = new Regex("name=\"([^\\\"]+)\"[^>]*>", RegexOptions.CultureInvariant).Match(xml, from, to - from); 99 | if (match.Success) 100 | { 101 | var name = match.Groups[1].Value; 102 | from = match.Index + match.Length; // end of element 103 | _docComments[name] = xml.Substring(from, to - from); // inner xml 104 | } 105 | 106 | // 107 | from = to; 108 | } 109 | } 110 | catch (Exception) 111 | { 112 | } 113 | } 114 | } 115 | } 116 | 117 | return _docComments.TryGetValue(documentationMemberID, out var docComment) ? docComment : string.Empty; 118 | } 119 | 120 | private static readonly XmlReaderSettings s_xmlSettings = new XmlReaderSettings() 121 | { 122 | DtdProcessing = DtdProcessing.Prohibit, 123 | CheckCharacters = false, 124 | IgnoreComments = true, 125 | ValidationFlags = System.Xml.Schema.XmlSchemaValidationFlags.None, 126 | IgnoreWhitespace = true, 127 | IgnoreProcessingInstructions = true, 128 | ConformanceLevel = ConformanceLevel.Auto, 129 | CloseInput = true, 130 | }; 131 | 132 | //private sealed class ContentBasedXmlDocumentationProvider : XmlDocumentationProvider 133 | //{ 134 | // private readonly byte[] _xmlDocCommentBytes; 135 | 136 | // public ContentBasedXmlDocumentationProvider(byte[] xmlDocCommentBytes) 137 | // { 138 | // _xmlDocCommentBytes = xmlDocCommentBytes; 139 | // } 140 | 141 | // protected override Stream GetSourceStream(CancellationToken cancellationToken) 142 | // { 143 | // return new MemoryStream(_xmlDocCommentBytes, false); 144 | // } 145 | 146 | // public override bool Equals(object obj) 147 | // { 148 | // var other = obj as ContentBasedXmlDocumentationProvider; 149 | // return other != null && EqualsHelper(other); 150 | // } 151 | 152 | // private bool EqualsHelper(ContentBasedXmlDocumentationProvider other) 153 | // { 154 | // // Check for reference equality first 155 | // if (this == other || _xmlDocCommentBytes == other._xmlDocCommentBytes) 156 | // { 157 | // return true; 158 | // } 159 | 160 | // // Compare byte sequences 161 | // if (_xmlDocCommentBytes.Length != other._xmlDocCommentBytes.Length) 162 | // { 163 | // return false; 164 | // } 165 | 166 | // for (var i = 0; i < _xmlDocCommentBytes.Length; i++) 167 | // { 168 | // if (_xmlDocCommentBytes[i] != other._xmlDocCommentBytes[i]) 169 | // { 170 | // return false; 171 | // } 172 | // } 173 | 174 | // return true; 175 | // } 176 | 177 | // public override int GetHashCode() 178 | // { 179 | // return Hash.CombineValues(_xmlDocCommentBytes); 180 | // } 181 | //} 182 | 183 | private sealed class FileBasedXmlDocumentationProvider : XmlDocumentationProvider 184 | { 185 | private readonly string _filePath; 186 | 187 | public FileBasedXmlDocumentationProvider(string filePath) 188 | { 189 | _filePath = filePath; 190 | } 191 | 192 | protected override Stream GetSourceStream(CancellationToken cancellationToken) 193 | { 194 | return new FileStream(_filePath, FileMode.Open, FileAccess.Read); 195 | } 196 | 197 | public override bool Equals(object obj) 198 | { 199 | var other = obj as FileBasedXmlDocumentationProvider; 200 | return other != null && _filePath == other._filePath; 201 | } 202 | 203 | public override int GetHashCode() 204 | { 205 | return _filePath.GetHashCode(); 206 | } 207 | } 208 | 209 | /// 210 | /// A trivial XmlDocumentationProvider which never returns documentation. 211 | /// 212 | private sealed class NullXmlDocumentationProvider : XmlDocumentationProvider 213 | { 214 | protected override string GetDocumentationForSymbol(string documentationMemberID, CultureInfo preferredCulture, CancellationToken cancellationToken = default) 215 | { 216 | return ""; 217 | } 218 | 219 | protected override Stream GetSourceStream(CancellationToken cancellationToken) 220 | { 221 | return new MemoryStream(); 222 | } 223 | 224 | public override bool Equals(object obj) 225 | { 226 | // Only one instance is expected to exist, so reference equality is fine. 227 | return (object)this == obj; 228 | } 229 | 230 | public override int GetHashCode() 231 | { 232 | return 0; 233 | } 234 | } 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /src/Peachpie.VSCode/.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | node_modules -------------------------------------------------------------------------------- /src/Peachpie.VSCode/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | { 3 | "version": "0.1.0", 4 | "configurations": [ 5 | { 6 | "name": "Launch Extension", 7 | "type": "extensionHost", 8 | "request": "launch", 9 | "runtimeExecutable": "${execPath}", 10 | "args": ["--extensionDevelopmentPath=${workspaceRoot}" ], 11 | "stopOnEntry": false, 12 | "sourceMaps": true, 13 | "outFiles": [ 14 | "${workspaceRoot}/out/src/**/*.js" 15 | ], 16 | "preLaunchTask": "npm" 17 | }, 18 | { 19 | "name": "Launch Tests", 20 | "type": "extensionHost", 21 | "request": "launch", 22 | "runtimeExecutable": "${execPath}", 23 | "args": ["--extensionDevelopmentPath=${workspaceRoot}", "--extensionTestsPath=${workspaceRoot}/out/test" ], 24 | "stopOnEntry": false, 25 | "sourceMaps": true, 26 | "outFiles": [ 27 | "${workspaceRoot}/out/test/**/*.js" 28 | ], 29 | "preLaunchTask": "npm" 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /src/Peachpie.VSCode/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "out": false // set this to true to hide the "out" folder with the compiled JS files 5 | }, 6 | "search.exclude": { 7 | "out": true // set this to false to include "out" folder in search results 8 | }, 9 | "typescript.tsdk": "./node_modules/typescript/lib" // we want to use the TS server from our node_modules folder to control its version 10 | } -------------------------------------------------------------------------------- /src/Peachpie.VSCode/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // Available variables which can be used inside of strings. 2 | // ${workspaceRoot}: the root folder of the team 3 | // ${file}: the current opened file 4 | // ${fileBasename}: the current opened file's basename 5 | // ${fileDirname}: the current opened file's dirname 6 | // ${fileExtname}: the current opened file's extension 7 | // ${cwd}: the current working directory of the spawned process 8 | 9 | // A task runner that calls a custom npm script that compiles the extension. 10 | { 11 | "version": "2.0.0", 12 | 13 | // we want to run npm 14 | "command": "npm", 15 | 16 | // we run the custom script "compile" as defined in package.json 17 | "args": ["run", "compile", "--loglevel", "silent"], 18 | 19 | // The tsc compiler is not started in watching mode 20 | "isWatching": false, 21 | 22 | // use the standard msCompile and tsc problem matchers to find compile problems in the output. 23 | "problemMatcher": [ 24 | "$tsc", 25 | "$msCompile" 26 | ], 27 | "tasks": [ 28 | { 29 | "label": "npm", 30 | "type": "shell", 31 | "command": "npm", 32 | "args": [ 33 | "run", 34 | "compile", 35 | "--loglevel", 36 | "silent" 37 | ], 38 | "problemMatcher": [ 39 | "$tsc", 40 | "$msCompile" 41 | ], 42 | "group": "build" 43 | } 44 | ] 45 | } -------------------------------------------------------------------------------- /src/Peachpie.VSCode/.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | out/test/** 4 | test/** 5 | src/** 6 | images/** 7 | **/*.map 8 | .gitignore 9 | tsconfig.json 10 | vsc-extension-quickstart.md 11 | *.ps1 -------------------------------------------------------------------------------- /src/Peachpie.VSCode/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Runtime updates: 2 | 3 | The extension uses corresponding version of the [PeachPie compiler](https://www.peachpie.io/) in order to provide diagnostics, editor help and project templates. See [release notes](https://github.com/peachpiecompiler/peachpie/releases) for more information about feature updates. 4 | 5 | ## Extension updates: 6 | 7 | ### 1.0.5 8 | 9 | - updates templates and code analysis to `1.0.0-preview5` 10 | - **requires .NET 5.0** 11 | 12 | ### 1.0.4 13 | 14 | - update templates and compiler to `1.0.0-preview4` 15 | - **requires .NET 5.0** 16 | 17 | ### 1.0.1 18 | 19 | - update templates and compiler to `1.0.0-preview2` 20 | - known issue: not working with .NET Sdk 5.0 rc1 21 | 22 | ### 1.0.0 23 | 24 | - update templates and compiler to `1.0.0-preview1` 25 | 26 | ### 0.9.991 27 | 28 | - include diagnostic fixed on Windows 29 | - syntactic and semantic diagnostics reported together 30 | 31 | ### 0.9.980 32 | 33 | ### 0.9.970 34 | 35 | - updated code analysis 36 | - updated runtime and standard libraries 37 | 38 | ### 0.9.950 39 | 40 | - updated code analysis 41 | - memory usage improvements 42 | - updated project templates 43 | 44 | ### 0.9.920 45 | 46 | - updated code analysis, fixes issues on dotnet 3.1.200 47 | 48 | ### 0.9.900 49 | 50 | - updated code analysis 51 | - updated peachpie version 52 | 53 | ### 0.9.800 54 | 55 | - updated code analysis 56 | 57 | ### 0.9.601 58 | 59 | - tooltips with detailed summary and remarks 60 | - tooltips for .NET classes and methods 61 | - styled tooltips 62 | - go to symbol location (go to definition) 63 | 64 | ### 0.9.600 65 | 66 | - fixed diagnostics 67 | 68 | ### 0.9.46 69 | ### 0.9.45 70 | ### 0.9.44 71 | ### 0.9.43 72 | ### 0.9.42 73 | 74 | - PeachPie runs on .NET Core 3.0 75 | 76 | ### 0.9.40 77 | 78 | - improved compatibility 79 | - bug fixes 80 | 81 | ### 0.9.37 82 | 83 | - update Peachpie 0.9.37 84 | - added new diagnostics 85 | - runtime improvements and fixes 86 | - compiler improvements and fixes 87 | - improved PHP standard library 88 | - updated project template 89 | 90 | ### 0.9.36 91 | 92 | - update Peachpie 0.9.36 93 | 94 | ### 0.9.35 95 | 96 | - update Peachpie 0.9.35 97 | 98 | ### 0.9.31 99 | 100 | - update PeachPie 0.9.31 101 | - new array implementation 102 | - compiler improvements and fixes 103 | - runtime improvements and fixes 104 | - general issues fixes 105 | - update project templates 106 | 107 | ### 0.9.26 108 | 109 | - update PeachPie 0.9.26 110 | 111 | ### 0.9.18 112 | - support for .NET generics 113 | - support for .NET custom attributes (on methods and classes) 114 | - [compiler fixes and improvements](https://github.com/peachpiecompiler/peachpie/releases/tag/v0.9.18) 115 | 116 | ### 0.9.16 117 | - bump to Peachpie Sdk 0.9.16 118 | - fixes 119 | 120 | ### 0.9.0 121 | - Project template targets PeachPie 0.9.0 122 | - A lot more diagnostics 123 | - PeachPie 0.9.0 enhancements includes traits, generators, compiler fixes 124 | 125 | ### 0.8.0 126 | - Projects target PeachPie 0.8.0 127 | 128 | ### 0.7.0 129 | - Re-enabled PeachPie project diagnostics 130 | - New projects target PeachPie 0.7.0 131 | 132 | ### 0.6.0 133 | - Support for new msbuild project format 134 | - `project.json` deprecated and removed 135 | - Updated `Create Project` command 136 | 137 | ### 0.5.0 138 | 139 | - New project refers to PeachPie 0.5.0 140 | - Underlining of syntax errors 141 | - Code Analysis diagnostics 142 | 143 | ### 0.3.0 144 | 145 | - Initial release 146 | -------------------------------------------------------------------------------- /src/Peachpie.VSCode/README.md: -------------------------------------------------------------------------------- 1 | # PeachPie Compiler for Visual Studio Code 2 | 3 | Welcome to the official extension of [PeachPie Compiler](https://www.peachpie.io/) - the PHP language compiler for .NET. 4 | 5 | The extension runs the compiler analysis in the background and extends the editor, providing PHP code diagnostics, enabling debugging, and helping with running PHP code on .NET. 6 | 7 | ### New Project 8 | 9 | Quickly create the project file for PHP on .NET within Visual Studio Code. For more information see our [Get Started](https://www.peachpie.io/getstarted) page. 10 | 11 | ![New Project Command](images/new-peachpie-project.gif) 12 | 13 | Go to VSCode's `Command Palette` and create the project. The command restores the required .NET dependencies and initializes `tasks.json` for you. 14 | 15 | ### Diagnostics 16 | 17 | Once the project is created, the compiler analysis runs in the background, providing a list of problems within the entire workspace. 18 | 19 | ![Compiler diagnostics](images/diagnostics.gif) 20 | 21 | ### Rich Tooltips 22 | 23 | Insights about resolved types and symbols are accessible through tooltips. They reveal how the compiler sees types and display remarks and descriptions of resolved symbols. 24 | 25 | ![Tooltips](images/tooltips.gif) 26 | 27 | ### Debugging 28 | 29 | The code runs on the .NET CLR, taking full advantage of its debugger and related features. 30 | 31 | - Set breakpoints and step through the code. 32 | - Check the *Output* panel for runtime events and warnings. 33 | - Watch the call stack, inspect variables, watch locals. 34 | 35 | ## Remarks 36 | 37 | - The [.NET Core SDK](https://dotnet.microsoft.com/download) is required. 38 | - [C# for Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=ms-vscode.csharp) is required. 39 | 40 | *PeachPie is a work in progress. Please see the [project repository](https://www.github.com/iolevel/peachpie) for issues or for ways how to contribute. Visit the [project website](https://www.peachpie.io/) for more information.* 41 | -------------------------------------------------------------------------------- /src/Peachpie.VSCode/icons/peachpie-icon-new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iolevel/peachpie-vscode/735f07480714946a8fe90184c86e5275feca881f/src/Peachpie.VSCode/icons/peachpie-icon-new.png -------------------------------------------------------------------------------- /src/Peachpie.VSCode/icons/peachpie-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iolevel/peachpie-vscode/735f07480714946a8fe90184c86e5275feca881f/src/Peachpie.VSCode/icons/peachpie-icon.png -------------------------------------------------------------------------------- /src/Peachpie.VSCode/icons/peachpie-vscode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iolevel/peachpie-vscode/735f07480714946a8fe90184c86e5275feca881f/src/Peachpie.VSCode/icons/peachpie-vscode.png -------------------------------------------------------------------------------- /src/Peachpie.VSCode/images/README.md: -------------------------------------------------------------------------------- 1 | # Images -------------------------------------------------------------------------------- /src/Peachpie.VSCode/images/Screen Shot 2017-04-17 at 14.59.19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iolevel/peachpie-vscode/735f07480714946a8fe90184c86e5275feca881f/src/Peachpie.VSCode/images/Screen Shot 2017-04-17 at 14.59.19.png -------------------------------------------------------------------------------- /src/Peachpie.VSCode/images/breakpoint-cropped.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iolevel/peachpie-vscode/735f07480714946a8fe90184c86e5275feca881f/src/Peachpie.VSCode/images/breakpoint-cropped.png -------------------------------------------------------------------------------- /src/Peachpie.VSCode/images/breakpoint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iolevel/peachpie-vscode/735f07480714946a8fe90184c86e5275feca881f/src/Peachpie.VSCode/images/breakpoint.png -------------------------------------------------------------------------------- /src/Peachpie.VSCode/images/create-project-cropped.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iolevel/peachpie-vscode/735f07480714946a8fe90184c86e5275feca881f/src/Peachpie.VSCode/images/create-project-cropped.png -------------------------------------------------------------------------------- /src/Peachpie.VSCode/images/create-project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iolevel/peachpie-vscode/735f07480714946a8fe90184c86e5275feca881f/src/Peachpie.VSCode/images/create-project.png -------------------------------------------------------------------------------- /src/Peachpie.VSCode/images/debug-cropped.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iolevel/peachpie-vscode/735f07480714946a8fe90184c86e5275feca881f/src/Peachpie.VSCode/images/debug-cropped.png -------------------------------------------------------------------------------- /src/Peachpie.VSCode/images/debug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iolevel/peachpie-vscode/735f07480714946a8fe90184c86e5275feca881f/src/Peachpie.VSCode/images/debug.png -------------------------------------------------------------------------------- /src/Peachpie.VSCode/images/diagnostics.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iolevel/peachpie-vscode/735f07480714946a8fe90184c86e5275feca881f/src/Peachpie.VSCode/images/diagnostics.gif -------------------------------------------------------------------------------- /src/Peachpie.VSCode/images/new-peachpie-project.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iolevel/peachpie-vscode/735f07480714946a8fe90184c86e5275feca881f/src/Peachpie.VSCode/images/new-peachpie-project.gif -------------------------------------------------------------------------------- /src/Peachpie.VSCode/images/peachpie-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iolevel/peachpie-vscode/735f07480714946a8fe90184c86e5275feca881f/src/Peachpie.VSCode/images/peachpie-icon.png -------------------------------------------------------------------------------- /src/Peachpie.VSCode/images/peachpie-round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iolevel/peachpie-vscode/735f07480714946a8fe90184c86e5275feca881f/src/Peachpie.VSCode/images/peachpie-round.png -------------------------------------------------------------------------------- /src/Peachpie.VSCode/images/project-icon.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iolevel/peachpie-vscode/735f07480714946a8fe90184c86e5275feca881f/src/Peachpie.VSCode/images/project-icon.psd -------------------------------------------------------------------------------- /src/Peachpie.VSCode/images/round-orange-196x196.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iolevel/peachpie-vscode/735f07480714946a8fe90184c86e5275feca881f/src/Peachpie.VSCode/images/round-orange-196x196.png -------------------------------------------------------------------------------- /src/Peachpie.VSCode/images/syntax-error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iolevel/peachpie-vscode/735f07480714946a8fe90184c86e5275feca881f/src/Peachpie.VSCode/images/syntax-error.png -------------------------------------------------------------------------------- /src/Peachpie.VSCode/images/tEDLQt.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iolevel/peachpie-vscode/735f07480714946a8fe90184c86e5275feca881f/src/Peachpie.VSCode/images/tEDLQt.gif -------------------------------------------------------------------------------- /src/Peachpie.VSCode/images/tooltips.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iolevel/peachpie-vscode/735f07480714946a8fe90184c86e5275feca881f/src/Peachpie.VSCode/images/tooltips.gif -------------------------------------------------------------------------------- /src/Peachpie.VSCode/images/unresolved-diagnostics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iolevel/peachpie-vscode/735f07480714946a8fe90184c86e5275feca881f/src/Peachpie.VSCode/images/unresolved-diagnostics.png -------------------------------------------------------------------------------- /src/Peachpie.VSCode/images/vscode-diagnostics-long-cropped.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iolevel/peachpie-vscode/735f07480714946a8fe90184c86e5275feca881f/src/Peachpie.VSCode/images/vscode-diagnostics-long-cropped.png -------------------------------------------------------------------------------- /src/Peachpie.VSCode/images/vscode-diagnostics-long.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iolevel/peachpie-vscode/735f07480714946a8fe90184c86e5275feca881f/src/Peachpie.VSCode/images/vscode-diagnostics-long.png -------------------------------------------------------------------------------- /src/Peachpie.VSCode/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "peachpie-vscode", 3 | "version": "1.0.23", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/mocha": { 8 | "version": "5.2.7", 9 | "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz", 10 | "integrity": "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==", 11 | "dev": true 12 | }, 13 | "@types/node": { 14 | "version": "10.17.60", 15 | "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", 16 | "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==", 17 | "dev": true 18 | }, 19 | "@types/vscode": { 20 | "version": "1.69.0", 21 | "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.69.0.tgz", 22 | "integrity": "sha512-RlzDAnGqUoo9wS6d4tthNyAdZLxOIddLiX3djMoWk29jFfSA1yJbIwr0epBYqqYarWB6s2Z+4VaZCQ80Jaa3kA==", 23 | "dev": true 24 | }, 25 | "@ungap/promise-all-settled": { 26 | "version": "1.1.2", 27 | "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", 28 | "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", 29 | "dev": true 30 | }, 31 | "ansi-colors": { 32 | "version": "4.1.1", 33 | "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", 34 | "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", 35 | "dev": true 36 | }, 37 | "ansi-regex": { 38 | "version": "5.0.1", 39 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 40 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 41 | "dev": true 42 | }, 43 | "ansi-styles": { 44 | "version": "4.3.0", 45 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 46 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 47 | "dev": true, 48 | "requires": { 49 | "color-convert": "^2.0.1" 50 | } 51 | }, 52 | "anymatch": { 53 | "version": "3.1.2", 54 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", 55 | "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", 56 | "dev": true, 57 | "requires": { 58 | "normalize-path": "^3.0.0", 59 | "picomatch": "^2.0.4" 60 | } 61 | }, 62 | "argparse": { 63 | "version": "2.0.1", 64 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", 65 | "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", 66 | "dev": true 67 | }, 68 | "balanced-match": { 69 | "version": "1.0.0", 70 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 71 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" 72 | }, 73 | "binary-extensions": { 74 | "version": "2.2.0", 75 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", 76 | "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", 77 | "dev": true 78 | }, 79 | "brace-expansion": { 80 | "version": "1.1.11", 81 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 82 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 83 | "requires": { 84 | "balanced-match": "^1.0.0", 85 | "concat-map": "0.0.1" 86 | } 87 | }, 88 | "braces": { 89 | "version": "3.0.2", 90 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", 91 | "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", 92 | "dev": true, 93 | "requires": { 94 | "fill-range": "^7.0.1" 95 | } 96 | }, 97 | "browser-stdout": { 98 | "version": "1.3.1", 99 | "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", 100 | "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", 101 | "dev": true 102 | }, 103 | "camelcase": { 104 | "version": "6.3.0", 105 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", 106 | "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", 107 | "dev": true 108 | }, 109 | "chalk": { 110 | "version": "4.1.2", 111 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 112 | "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 113 | "dev": true, 114 | "requires": { 115 | "ansi-styles": "^4.1.0", 116 | "supports-color": "^7.1.0" 117 | }, 118 | "dependencies": { 119 | "has-flag": { 120 | "version": "4.0.0", 121 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 122 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 123 | "dev": true 124 | }, 125 | "supports-color": { 126 | "version": "7.2.0", 127 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 128 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 129 | "dev": true, 130 | "requires": { 131 | "has-flag": "^4.0.0" 132 | } 133 | } 134 | } 135 | }, 136 | "chokidar": { 137 | "version": "3.5.3", 138 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", 139 | "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", 140 | "dev": true, 141 | "requires": { 142 | "anymatch": "~3.1.2", 143 | "braces": "~3.0.2", 144 | "fsevents": "~2.3.2", 145 | "glob-parent": "~5.1.2", 146 | "is-binary-path": "~2.1.0", 147 | "is-glob": "~4.0.1", 148 | "normalize-path": "~3.0.0", 149 | "readdirp": "~3.6.0" 150 | } 151 | }, 152 | "cliui": { 153 | "version": "7.0.4", 154 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", 155 | "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", 156 | "dev": true, 157 | "requires": { 158 | "string-width": "^4.2.0", 159 | "strip-ansi": "^6.0.0", 160 | "wrap-ansi": "^7.0.0" 161 | } 162 | }, 163 | "color-convert": { 164 | "version": "2.0.1", 165 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 166 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 167 | "dev": true, 168 | "requires": { 169 | "color-name": "~1.1.4" 170 | } 171 | }, 172 | "color-name": { 173 | "version": "1.1.4", 174 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 175 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 176 | "dev": true 177 | }, 178 | "concat-map": { 179 | "version": "0.0.1", 180 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 181 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" 182 | }, 183 | "debug": { 184 | "version": "4.3.3", 185 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", 186 | "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", 187 | "dev": true, 188 | "requires": { 189 | "ms": "2.1.2" 190 | }, 191 | "dependencies": { 192 | "ms": { 193 | "version": "2.1.2", 194 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 195 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", 196 | "dev": true 197 | } 198 | } 199 | }, 200 | "decamelize": { 201 | "version": "4.0.0", 202 | "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", 203 | "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", 204 | "dev": true 205 | }, 206 | "define-lazy-prop": { 207 | "version": "2.0.0", 208 | "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", 209 | "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==" 210 | }, 211 | "diff": { 212 | "version": "5.0.0", 213 | "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", 214 | "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", 215 | "dev": true 216 | }, 217 | "emoji-regex": { 218 | "version": "8.0.0", 219 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 220 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 221 | "dev": true 222 | }, 223 | "esbuild": { 224 | "version": "0.14.49", 225 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.49.tgz", 226 | "integrity": "sha512-/TlVHhOaq7Yz8N1OJrjqM3Auzo5wjvHFLk+T8pIue+fhnhIMpfAzsG6PLVMbFveVxqD2WOp3QHei+52IMUNmCw==", 227 | "dev": true, 228 | "requires": { 229 | "esbuild-android-64": "0.14.49", 230 | "esbuild-android-arm64": "0.14.49", 231 | "esbuild-darwin-64": "0.14.49", 232 | "esbuild-darwin-arm64": "0.14.49", 233 | "esbuild-freebsd-64": "0.14.49", 234 | "esbuild-freebsd-arm64": "0.14.49", 235 | "esbuild-linux-32": "0.14.49", 236 | "esbuild-linux-64": "0.14.49", 237 | "esbuild-linux-arm": "0.14.49", 238 | "esbuild-linux-arm64": "0.14.49", 239 | "esbuild-linux-mips64le": "0.14.49", 240 | "esbuild-linux-ppc64le": "0.14.49", 241 | "esbuild-linux-riscv64": "0.14.49", 242 | "esbuild-linux-s390x": "0.14.49", 243 | "esbuild-netbsd-64": "0.14.49", 244 | "esbuild-openbsd-64": "0.14.49", 245 | "esbuild-sunos-64": "0.14.49", 246 | "esbuild-windows-32": "0.14.49", 247 | "esbuild-windows-64": "0.14.49", 248 | "esbuild-windows-arm64": "0.14.49" 249 | } 250 | }, 251 | "esbuild-android-64": { 252 | "version": "0.14.49", 253 | "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.49.tgz", 254 | "integrity": "sha512-vYsdOTD+yi+kquhBiFWl3tyxnj2qZJsl4tAqwhT90ktUdnyTizgle7TjNx6Ar1bN7wcwWqZ9QInfdk2WVagSww==", 255 | "dev": true, 256 | "optional": true 257 | }, 258 | "esbuild-android-arm64": { 259 | "version": "0.14.49", 260 | "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.49.tgz", 261 | "integrity": "sha512-g2HGr/hjOXCgSsvQZ1nK4nW/ei8JUx04Li74qub9qWrStlysaVmadRyTVuW32FGIpLQyc5sUjjZopj49eGGM2g==", 262 | "dev": true, 263 | "optional": true 264 | }, 265 | "esbuild-darwin-64": { 266 | "version": "0.14.49", 267 | "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.49.tgz", 268 | "integrity": "sha512-3rvqnBCtX9ywso5fCHixt2GBCUsogNp9DjGmvbBohh31Ces34BVzFltMSxJpacNki96+WIcX5s/vum+ckXiLYg==", 269 | "dev": true, 270 | "optional": true 271 | }, 272 | "esbuild-darwin-arm64": { 273 | "version": "0.14.49", 274 | "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.49.tgz", 275 | "integrity": "sha512-XMaqDxO846srnGlUSJnwbijV29MTKUATmOLyQSfswbK/2X5Uv28M9tTLUJcKKxzoo9lnkYPsx2o8EJcTYwCs/A==", 276 | "dev": true, 277 | "optional": true 278 | }, 279 | "esbuild-freebsd-64": { 280 | "version": "0.14.49", 281 | "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.49.tgz", 282 | "integrity": "sha512-NJ5Q6AjV879mOHFri+5lZLTp5XsO2hQ+KSJYLbfY9DgCu8s6/Zl2prWXVANYTeCDLlrIlNNYw8y34xqyLDKOmQ==", 283 | "dev": true, 284 | "optional": true 285 | }, 286 | "esbuild-freebsd-arm64": { 287 | "version": "0.14.49", 288 | "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.49.tgz", 289 | "integrity": "sha512-lFLtgXnAc3eXYqj5koPlBZvEbBSOSUbWO3gyY/0+4lBdRqELyz4bAuamHvmvHW5swJYL7kngzIZw6kdu25KGOA==", 290 | "dev": true, 291 | "optional": true 292 | }, 293 | "esbuild-linux-32": { 294 | "version": "0.14.49", 295 | "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.49.tgz", 296 | "integrity": "sha512-zTTH4gr2Kb8u4QcOpTDVn7Z8q7QEIvFl/+vHrI3cF6XOJS7iEI1FWslTo3uofB2+mn6sIJEQD9PrNZKoAAMDiA==", 297 | "dev": true, 298 | "optional": true 299 | }, 300 | "esbuild-linux-64": { 301 | "version": "0.14.49", 302 | "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.49.tgz", 303 | "integrity": "sha512-hYmzRIDzFfLrB5c1SknkxzM8LdEUOusp6M2TnuQZJLRtxTgyPnZZVtyMeCLki0wKgYPXkFsAVhi8vzo2mBNeTg==", 304 | "dev": true, 305 | "optional": true 306 | }, 307 | "esbuild-linux-arm": { 308 | "version": "0.14.49", 309 | "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.49.tgz", 310 | "integrity": "sha512-iE3e+ZVv1Qz1Sy0gifIsarJMQ89Rpm9mtLSRtG3AH0FPgAzQ5Z5oU6vYzhc/3gSPi2UxdCOfRhw2onXuFw/0lg==", 311 | "dev": true, 312 | "optional": true 313 | }, 314 | "esbuild-linux-arm64": { 315 | "version": "0.14.49", 316 | "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.49.tgz", 317 | "integrity": "sha512-KLQ+WpeuY+7bxukxLz5VgkAAVQxUv67Ft4DmHIPIW+2w3ObBPQhqNoeQUHxopoW/aiOn3m99NSmSV+bs4BSsdA==", 318 | "dev": true, 319 | "optional": true 320 | }, 321 | "esbuild-linux-mips64le": { 322 | "version": "0.14.49", 323 | "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.49.tgz", 324 | "integrity": "sha512-n+rGODfm8RSum5pFIqFQVQpYBw+AztL8s6o9kfx7tjfK0yIGF6tm5HlG6aRjodiiKkH2xAiIM+U4xtQVZYU4rA==", 325 | "dev": true, 326 | "optional": true 327 | }, 328 | "esbuild-linux-ppc64le": { 329 | "version": "0.14.49", 330 | "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.49.tgz", 331 | "integrity": "sha512-WP9zR4HX6iCBmMFH+XHHng2LmdoIeUmBpL4aL2TR8ruzXyT4dWrJ5BSbT8iNo6THN8lod6GOmYDLq/dgZLalGw==", 332 | "dev": true, 333 | "optional": true 334 | }, 335 | "esbuild-linux-riscv64": { 336 | "version": "0.14.49", 337 | "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.49.tgz", 338 | "integrity": "sha512-h66ORBz+Dg+1KgLvzTVQEA1LX4XBd1SK0Fgbhhw4akpG/YkN8pS6OzYI/7SGENiN6ao5hETRDSkVcvU9NRtkMQ==", 339 | "dev": true, 340 | "optional": true 341 | }, 342 | "esbuild-linux-s390x": { 343 | "version": "0.14.49", 344 | "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.49.tgz", 345 | "integrity": "sha512-DhrUoFVWD+XmKO1y7e4kNCqQHPs6twz6VV6Uezl/XHYGzM60rBewBF5jlZjG0nCk5W/Xy6y1xWeopkrhFFM0sQ==", 346 | "dev": true, 347 | "optional": true 348 | }, 349 | "esbuild-netbsd-64": { 350 | "version": "0.14.49", 351 | "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.49.tgz", 352 | "integrity": "sha512-BXaUwFOfCy2T+hABtiPUIpWjAeWK9P8O41gR4Pg73hpzoygVGnj0nI3YK4SJhe52ELgtdgWP/ckIkbn2XaTxjQ==", 353 | "dev": true, 354 | "optional": true 355 | }, 356 | "esbuild-openbsd-64": { 357 | "version": "0.14.49", 358 | "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.49.tgz", 359 | "integrity": "sha512-lP06UQeLDGmVPw9Rg437Btu6J9/BmyhdoefnQ4gDEJTtJvKtQaUcOQrhjTq455ouZN4EHFH1h28WOJVANK41kA==", 360 | "dev": true, 361 | "optional": true 362 | }, 363 | "esbuild-sunos-64": { 364 | "version": "0.14.49", 365 | "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.49.tgz", 366 | "integrity": "sha512-4c8Zowp+V3zIWje329BeLbGh6XI9c/rqARNaj5yPHdC61pHI9UNdDxT3rePPJeWcEZVKjkiAS6AP6kiITp7FSw==", 367 | "dev": true, 368 | "optional": true 369 | }, 370 | "esbuild-windows-32": { 371 | "version": "0.14.49", 372 | "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.49.tgz", 373 | "integrity": "sha512-q7Rb+J9yHTeKr9QTPDYkqfkEj8/kcKz9lOabDuvEXpXuIcosWCJgo5Z7h/L4r7rbtTH4a8U2FGKb6s1eeOHmJA==", 374 | "dev": true, 375 | "optional": true 376 | }, 377 | "esbuild-windows-64": { 378 | "version": "0.14.49", 379 | "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.49.tgz", 380 | "integrity": "sha512-+Cme7Ongv0UIUTniPqfTX6mJ8Deo7VXw9xN0yJEN1lQMHDppTNmKwAM3oGbD/Vqff+07K2gN0WfNkMohmG+dVw==", 381 | "dev": true, 382 | "optional": true 383 | }, 384 | "esbuild-windows-arm64": { 385 | "version": "0.14.49", 386 | "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.49.tgz", 387 | "integrity": "sha512-v+HYNAXzuANrCbbLFJ5nmO3m5y2PGZWLe3uloAkLt87aXiO2mZr3BTmacZdjwNkNEHuH3bNtN8cak+mzVjVPfA==", 388 | "dev": true, 389 | "optional": true 390 | }, 391 | "escalade": { 392 | "version": "3.1.1", 393 | "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", 394 | "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", 395 | "dev": true 396 | }, 397 | "escape-string-regexp": { 398 | "version": "4.0.0", 399 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", 400 | "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", 401 | "dev": true 402 | }, 403 | "fill-range": { 404 | "version": "7.0.1", 405 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", 406 | "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", 407 | "dev": true, 408 | "requires": { 409 | "to-regex-range": "^5.0.1" 410 | } 411 | }, 412 | "find-up": { 413 | "version": "5.0.0", 414 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", 415 | "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", 416 | "dev": true, 417 | "requires": { 418 | "locate-path": "^6.0.0", 419 | "path-exists": "^4.0.0" 420 | } 421 | }, 422 | "flat": { 423 | "version": "5.0.2", 424 | "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", 425 | "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", 426 | "dev": true 427 | }, 428 | "fs.realpath": { 429 | "version": "1.0.0", 430 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 431 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 432 | "dev": true 433 | }, 434 | "fsevents": { 435 | "version": "2.3.2", 436 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", 437 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", 438 | "dev": true, 439 | "optional": true 440 | }, 441 | "get-caller-file": { 442 | "version": "2.0.5", 443 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", 444 | "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", 445 | "dev": true 446 | }, 447 | "glob": { 448 | "version": "7.2.0", 449 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", 450 | "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", 451 | "dev": true, 452 | "requires": { 453 | "fs.realpath": "^1.0.0", 454 | "inflight": "^1.0.4", 455 | "inherits": "2", 456 | "minimatch": "^3.0.4", 457 | "once": "^1.3.0", 458 | "path-is-absolute": "^1.0.0" 459 | }, 460 | "dependencies": { 461 | "minimatch": { 462 | "version": "3.1.2", 463 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 464 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 465 | "dev": true, 466 | "requires": { 467 | "brace-expansion": "^1.1.7" 468 | } 469 | } 470 | } 471 | }, 472 | "glob-parent": { 473 | "version": "5.1.2", 474 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 475 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 476 | "dev": true, 477 | "requires": { 478 | "is-glob": "^4.0.1" 479 | } 480 | }, 481 | "growl": { 482 | "version": "1.10.5", 483 | "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", 484 | "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", 485 | "dev": true 486 | }, 487 | "inflight": { 488 | "version": "1.0.6", 489 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 490 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 491 | "dev": true, 492 | "requires": { 493 | "once": "^1.3.0", 494 | "wrappy": "1" 495 | } 496 | }, 497 | "inherits": { 498 | "version": "2.0.4", 499 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 500 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 501 | "dev": true 502 | }, 503 | "is-binary-path": { 504 | "version": "2.1.0", 505 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", 506 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", 507 | "dev": true, 508 | "requires": { 509 | "binary-extensions": "^2.0.0" 510 | } 511 | }, 512 | "is-docker": { 513 | "version": "2.2.1", 514 | "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", 515 | "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==" 516 | }, 517 | "is-extglob": { 518 | "version": "2.1.1", 519 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 520 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 521 | "dev": true 522 | }, 523 | "is-fullwidth-code-point": { 524 | "version": "3.0.0", 525 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 526 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 527 | "dev": true 528 | }, 529 | "is-glob": { 530 | "version": "4.0.3", 531 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 532 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 533 | "dev": true, 534 | "requires": { 535 | "is-extglob": "^2.1.1" 536 | } 537 | }, 538 | "is-number": { 539 | "version": "7.0.0", 540 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 541 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 542 | "dev": true 543 | }, 544 | "is-plain-obj": { 545 | "version": "2.1.0", 546 | "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", 547 | "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", 548 | "dev": true 549 | }, 550 | "is-unicode-supported": { 551 | "version": "0.1.0", 552 | "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", 553 | "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", 554 | "dev": true 555 | }, 556 | "is-wsl": { 557 | "version": "2.2.0", 558 | "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", 559 | "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", 560 | "requires": { 561 | "is-docker": "^2.0.0" 562 | } 563 | }, 564 | "isexe": { 565 | "version": "2.0.0", 566 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 567 | "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", 568 | "dev": true 569 | }, 570 | "js-yaml": { 571 | "version": "4.1.0", 572 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", 573 | "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", 574 | "dev": true, 575 | "requires": { 576 | "argparse": "^2.0.1" 577 | } 578 | }, 579 | "locate-path": { 580 | "version": "6.0.0", 581 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", 582 | "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", 583 | "dev": true, 584 | "requires": { 585 | "p-locate": "^5.0.0" 586 | } 587 | }, 588 | "log-symbols": { 589 | "version": "4.1.0", 590 | "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", 591 | "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", 592 | "dev": true, 593 | "requires": { 594 | "chalk": "^4.1.0", 595 | "is-unicode-supported": "^0.1.0" 596 | } 597 | }, 598 | "minimatch": { 599 | "version": "4.2.1", 600 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-4.2.1.tgz", 601 | "integrity": "sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g==", 602 | "dev": true, 603 | "requires": { 604 | "brace-expansion": "^1.1.7" 605 | } 606 | }, 607 | "mocha": { 608 | "version": "9.2.2", 609 | "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.2.tgz", 610 | "integrity": "sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g==", 611 | "dev": true, 612 | "requires": { 613 | "@ungap/promise-all-settled": "1.1.2", 614 | "ansi-colors": "4.1.1", 615 | "browser-stdout": "1.3.1", 616 | "chokidar": "3.5.3", 617 | "debug": "4.3.3", 618 | "diff": "5.0.0", 619 | "escape-string-regexp": "4.0.0", 620 | "find-up": "5.0.0", 621 | "glob": "7.2.0", 622 | "growl": "1.10.5", 623 | "he": "1.2.0", 624 | "js-yaml": "4.1.0", 625 | "log-symbols": "4.1.0", 626 | "minimatch": "4.2.1", 627 | "ms": "2.1.3", 628 | "nanoid": "3.3.1", 629 | "serialize-javascript": "6.0.0", 630 | "strip-json-comments": "3.1.1", 631 | "supports-color": "8.1.1", 632 | "which": "2.0.2", 633 | "workerpool": "6.2.0", 634 | "yargs": "16.2.0", 635 | "yargs-parser": "20.2.4", 636 | "yargs-unparser": "2.0.0" 637 | }, 638 | "dependencies": { 639 | "he": { 640 | "version": "1.2.0", 641 | "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", 642 | "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", 643 | "dev": true 644 | } 645 | } 646 | }, 647 | "ms": { 648 | "version": "2.1.3", 649 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 650 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 651 | "dev": true 652 | }, 653 | "nanoid": { 654 | "version": "3.3.1", 655 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", 656 | "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==", 657 | "dev": true 658 | }, 659 | "normalize-path": { 660 | "version": "3.0.0", 661 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", 662 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", 663 | "dev": true 664 | }, 665 | "once": { 666 | "version": "1.4.0", 667 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 668 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 669 | "dev": true, 670 | "requires": { 671 | "wrappy": "1" 672 | } 673 | }, 674 | "open": { 675 | "version": "8.4.0", 676 | "resolved": "https://registry.npmjs.org/open/-/open-8.4.0.tgz", 677 | "integrity": "sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==", 678 | "requires": { 679 | "define-lazy-prop": "^2.0.0", 680 | "is-docker": "^2.1.1", 681 | "is-wsl": "^2.2.0" 682 | } 683 | }, 684 | "p-limit": { 685 | "version": "3.1.0", 686 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", 687 | "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", 688 | "dev": true, 689 | "requires": { 690 | "yocto-queue": "^0.1.0" 691 | } 692 | }, 693 | "p-locate": { 694 | "version": "5.0.0", 695 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", 696 | "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", 697 | "dev": true, 698 | "requires": { 699 | "p-limit": "^3.0.2" 700 | } 701 | }, 702 | "path-exists": { 703 | "version": "4.0.0", 704 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", 705 | "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", 706 | "dev": true 707 | }, 708 | "path-is-absolute": { 709 | "version": "1.0.1", 710 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 711 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 712 | "dev": true 713 | }, 714 | "picomatch": { 715 | "version": "2.3.1", 716 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 717 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 718 | "dev": true 719 | }, 720 | "randombytes": { 721 | "version": "2.1.0", 722 | "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", 723 | "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", 724 | "dev": true, 725 | "requires": { 726 | "safe-buffer": "^5.1.0" 727 | } 728 | }, 729 | "readdirp": { 730 | "version": "3.6.0", 731 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", 732 | "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", 733 | "dev": true, 734 | "requires": { 735 | "picomatch": "^2.2.1" 736 | } 737 | }, 738 | "require-directory": { 739 | "version": "2.1.1", 740 | "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", 741 | "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", 742 | "dev": true 743 | }, 744 | "safe-buffer": { 745 | "version": "5.2.0", 746 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", 747 | "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==", 748 | "dev": true 749 | }, 750 | "semver": { 751 | "version": "7.3.7", 752 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", 753 | "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", 754 | "requires": { 755 | "lru-cache": "^6.0.0" 756 | }, 757 | "dependencies": { 758 | "lru-cache": { 759 | "version": "6.0.0", 760 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", 761 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", 762 | "requires": { 763 | "yallist": "^4.0.0" 764 | } 765 | } 766 | } 767 | }, 768 | "serialize-javascript": { 769 | "version": "6.0.0", 770 | "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", 771 | "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", 772 | "dev": true, 773 | "requires": { 774 | "randombytes": "^2.1.0" 775 | } 776 | }, 777 | "string-width": { 778 | "version": "4.2.3", 779 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 780 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 781 | "dev": true, 782 | "requires": { 783 | "emoji-regex": "^8.0.0", 784 | "is-fullwidth-code-point": "^3.0.0", 785 | "strip-ansi": "^6.0.1" 786 | } 787 | }, 788 | "strip-ansi": { 789 | "version": "6.0.1", 790 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 791 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 792 | "dev": true, 793 | "requires": { 794 | "ansi-regex": "^5.0.1" 795 | } 796 | }, 797 | "strip-json-comments": { 798 | "version": "3.1.1", 799 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", 800 | "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", 801 | "dev": true 802 | }, 803 | "supports-color": { 804 | "version": "8.1.1", 805 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", 806 | "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", 807 | "dev": true, 808 | "requires": { 809 | "has-flag": "^4.0.0" 810 | }, 811 | "dependencies": { 812 | "has-flag": { 813 | "version": "4.0.0", 814 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 815 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 816 | "dev": true 817 | } 818 | } 819 | }, 820 | "to-regex-range": { 821 | "version": "5.0.1", 822 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 823 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 824 | "dev": true, 825 | "requires": { 826 | "is-number": "^7.0.0" 827 | } 828 | }, 829 | "typescript": { 830 | "version": "4.7.4", 831 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", 832 | "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", 833 | "dev": true 834 | }, 835 | "vscode-jsonrpc": { 836 | "version": "6.0.0", 837 | "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0.tgz", 838 | "integrity": "sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg==" 839 | }, 840 | "vscode-languageclient": { 841 | "version": "7.0.0", 842 | "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-7.0.0.tgz", 843 | "integrity": "sha512-P9AXdAPlsCgslpP9pRxYPqkNYV7Xq8300/aZDpO35j1fJm/ncize8iGswzYlcvFw5DQUx4eVk+KvfXdL0rehNg==", 844 | "requires": { 845 | "minimatch": "^3.0.4", 846 | "semver": "^7.3.4", 847 | "vscode-languageserver-protocol": "3.16.0" 848 | }, 849 | "dependencies": { 850 | "minimatch": { 851 | "version": "3.1.2", 852 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 853 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 854 | "requires": { 855 | "brace-expansion": "^1.1.7" 856 | } 857 | } 858 | } 859 | }, 860 | "vscode-languageserver": { 861 | "version": "7.0.0", 862 | "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-7.0.0.tgz", 863 | "integrity": "sha512-60HTx5ID+fLRcgdHfmz0LDZAXYEV68fzwG0JWwEPBode9NuMYTIxuYXPg4ngO8i8+Ou0lM7y6GzaYWbiDL0drw==", 864 | "requires": { 865 | "vscode-languageserver-protocol": "3.16.0" 866 | } 867 | }, 868 | "vscode-languageserver-protocol": { 869 | "version": "3.16.0", 870 | "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.16.0.tgz", 871 | "integrity": "sha512-sdeUoAawceQdgIfTI+sdcwkiK2KU+2cbEYA0agzM2uqaUy2UpnnGHtWTHVEtS0ES4zHU0eMFRGN+oQgDxlD66A==", 872 | "requires": { 873 | "vscode-jsonrpc": "6.0.0", 874 | "vscode-languageserver-types": "3.16.0" 875 | } 876 | }, 877 | "vscode-languageserver-types": { 878 | "version": "3.16.0", 879 | "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz", 880 | "integrity": "sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA==" 881 | }, 882 | "vscode-nls": { 883 | "version": "3.2.5", 884 | "resolved": "https://registry.npmjs.org/vscode-nls/-/vscode-nls-3.2.5.tgz", 885 | "integrity": "sha512-ITtoh3V4AkWXMmp3TB97vsMaHRgHhsSFPsUdzlueSL+dRZbSNTZeOmdQv60kjCV306ghPxhDeoNUEm3+EZMuyw==" 886 | }, 887 | "which": { 888 | "version": "2.0.2", 889 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 890 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 891 | "dev": true, 892 | "requires": { 893 | "isexe": "^2.0.0" 894 | } 895 | }, 896 | "workerpool": { 897 | "version": "6.2.0", 898 | "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz", 899 | "integrity": "sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==", 900 | "dev": true 901 | }, 902 | "wrap-ansi": { 903 | "version": "7.0.0", 904 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", 905 | "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", 906 | "dev": true, 907 | "requires": { 908 | "ansi-styles": "^4.0.0", 909 | "string-width": "^4.1.0", 910 | "strip-ansi": "^6.0.0" 911 | } 912 | }, 913 | "wrappy": { 914 | "version": "1.0.2", 915 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 916 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 917 | "dev": true 918 | }, 919 | "xmldom": { 920 | "version": "0.6.0", 921 | "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.6.0.tgz", 922 | "integrity": "sha512-iAcin401y58LckRZ0TkI4k0VSM1Qg0KGSc3i8rU+xrxe19A/BN1zHyVSJY7uoutVlaTSzYyk/v5AmkewAP7jtg==" 923 | }, 924 | "xmlhttprequest": { 925 | "version": "1.8.0", 926 | "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", 927 | "integrity": "sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw=" 928 | }, 929 | "xpath": { 930 | "version": "0.0.27", 931 | "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.27.tgz", 932 | "integrity": "sha512-fg03WRxtkCV6ohClePNAECYsmpKKTv5L8y/X3Dn1hQrec3POx2jHZ/0P2qQ6HvsrU1BmeqXcof3NGGueG6LxwQ==" 933 | }, 934 | "y18n": { 935 | "version": "5.0.8", 936 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", 937 | "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", 938 | "dev": true 939 | }, 940 | "yallist": { 941 | "version": "4.0.0", 942 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 943 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" 944 | }, 945 | "yargs": { 946 | "version": "16.2.0", 947 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", 948 | "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", 949 | "dev": true, 950 | "requires": { 951 | "cliui": "^7.0.2", 952 | "escalade": "^3.1.1", 953 | "get-caller-file": "^2.0.5", 954 | "require-directory": "^2.1.1", 955 | "string-width": "^4.2.0", 956 | "y18n": "^5.0.5", 957 | "yargs-parser": "^20.2.2" 958 | } 959 | }, 960 | "yargs-parser": { 961 | "version": "20.2.4", 962 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", 963 | "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", 964 | "dev": true 965 | }, 966 | "yargs-unparser": { 967 | "version": "2.0.0", 968 | "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", 969 | "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", 970 | "dev": true, 971 | "requires": { 972 | "camelcase": "^6.0.0", 973 | "decamelize": "^4.0.0", 974 | "flat": "^5.0.2", 975 | "is-plain-obj": "^2.1.0" 976 | } 977 | }, 978 | "yocto-queue": { 979 | "version": "0.1.0", 980 | "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", 981 | "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", 982 | "dev": true 983 | } 984 | } 985 | } 986 | -------------------------------------------------------------------------------- /src/Peachpie.VSCode/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "peachpie-vscode", 3 | "displayName": "PeachPie for Visual Studio Code", 4 | "description": "Visual Studio Code extension for PeachPie - the PHP compiler for .NET and .NET Core.", 5 | "icon": "icons/peachpie-vscode.png", 6 | "version": "1.0.23", 7 | "publisher": "iolevel", 8 | "preview": true, 9 | "license": "Apache-2.0", 10 | "galleryBanner": { 11 | "color": "#eff1f3", 12 | "theme": "light" 13 | }, 14 | "homepage": "https://www.peachpie.io/", 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/peachpiecompiler/peachpie.git" 18 | }, 19 | "engines": { 20 | "vscode": "^1.63.1", 21 | "node": "^8.11.0" 22 | }, 23 | "categories": [ 24 | "Programming Languages", 25 | "Snippets", 26 | "Debuggers" 27 | ], 28 | "keywords": [ 29 | "PeachPie", 30 | "coreclr", 31 | "dotnet", 32 | "php" 33 | ], 34 | "activationEvents": [ 35 | "onLanguage:php", 36 | "onCommand:peachpie.createconsole", 37 | "onCommand:peachpie.createlibrary" 38 | ], 39 | "main": "./out/src/extension", 40 | "contributes": { 41 | "commands": [ 42 | { 43 | "command": "peachpie.createconsole", 44 | "title": "PeachPie: Create console project" 45 | }, 46 | { 47 | "command": "peachpie.createlibrary", 48 | "title": "PeachPie: Create a class library project" 49 | } 50 | ], 51 | "breakpoints": [ 52 | { 53 | "language": "php" 54 | } 55 | ], 56 | "debuggers": [ 57 | { 58 | "type": "dummy", 59 | "enableBreakpointsFor": { 60 | "languageIds": [ 61 | "php" 62 | ] 63 | } 64 | } 65 | ], 66 | "languages": [ 67 | { 68 | "id": "xml", 69 | "extensions": [ 70 | ".msbuildproj" 71 | ] 72 | } 73 | ] 74 | }, 75 | "badges": [ 76 | { 77 | "url": "https://api.travis-ci.org/peachpiecompiler/peachpie.svg?branch=master", 78 | "href": "https://travis-ci.org/peachpiecompiler/peachpie", 79 | "description": "Build Status" 80 | }, 81 | { 82 | "url": "https://badges.gitter.im/iolevel/peachpie.svg", 83 | "href": "https://gitter.im/iolevel/peachpie?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge", 84 | "description": "Chat on Gitter" 85 | } 86 | ], 87 | "scripts": { 88 | "esbuild-base": "esbuild ./src/extension.ts --bundle --outfile=out/src/extension.js --external:vscode --format=cjs --platform=node", 89 | "vscode:prepublish": "dotnet publish ../Peachpie.LanguageServer --configuration Release --output ../Peachpie.VSCode/out/server & npm run -S esbuild-base -- --minify", 90 | "compile": "dotnet publish ../Peachpie.LanguageServer --output ../Peachpie.VSCode/out/server & npm run -S esbuild-base -- --sourcemap", 91 | "postinstall": "" 92 | }, 93 | "devDependencies": { 94 | "@types/mocha": "^5.2.7", 95 | "@types/node": "^10.17.60", 96 | "@types/vscode": "^1.63.1", 97 | "esbuild": "^0.14.48", 98 | "mocha": "^9.2.2", 99 | "typescript": "^4.7.4" 100 | }, 101 | "dependencies": { 102 | "vscode-languageclient": "^7.0.0", 103 | "vscode-languageserver": "^7.0.0", 104 | "vscode-nls": "^3.2.2", 105 | "xmldom": "^0.6.0", 106 | "xpath": "^0.0.27", 107 | "open": "^8.4.0", 108 | "xmlhttprequest": "^1.8.0" 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/Peachpie.VSCode/publish.ps1: -------------------------------------------------------------------------------- 1 | vsce publish --baseImagesUrl https://raw.githubusercontent.com/iolevel/peachpie-vscode/master/src/Peachpie.VSCode -------------------------------------------------------------------------------- /src/Peachpie.VSCode/src/defaults.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export var defaultTasksJson = 4 | { 5 | // See https://go.microsoft.com/fwlink/?LinkId=733558 6 | // for the documentation about the tasks.json format 7 | "version": "2.0.0", 8 | "tasks": [ 9 | { 10 | "label": "build", 11 | "command": "dotnet", 12 | "type": "shell", 13 | "args": [ 14 | "build", 15 | // Ask dotnet build to generate full paths for file names. 16 | "/property:GenerateFullPaths=true", 17 | // Do not generate summary otherwise it leads to duplicate errors in Problems panel 18 | "/consoleloggerparameters:NoSummary" 19 | ], 20 | "group": "build", 21 | "presentation": { 22 | "reveal": "silent" 23 | }, 24 | "problemMatcher": "$msCompile" 25 | } 26 | ] 27 | }; 28 | 29 | export var defaultLaunchJson = 30 | { 31 | "version": "0.2.0", 32 | "configurations": [ 33 | { 34 | "name": ".NET Core Launch (console)", 35 | "type": "coreclr", 36 | "request": "launch", 37 | "preLaunchTask": "build", 38 | "program": "${workspaceRoot}/bin/Debug/net6.0/console.dll", 39 | "args": [], 40 | "cwd": "${workspaceRoot}", 41 | "externalConsole": false, 42 | "stopAtEntry": false, 43 | "internalConsoleOptions": "openOnSessionStart" 44 | }, 45 | { 46 | "name": ".NET Core Attach", 47 | "type": "coreclr", 48 | "request": "attach", 49 | "processId": "${command.pickProcess}" 50 | } 51 | ] 52 | }; -------------------------------------------------------------------------------- /src/Peachpie.VSCode/src/extension.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | // The module 'vscode' contains the VS Code extensibility API 3 | // Import the module and reference it with the alias vscode in your code below 4 | import * as vscode from 'vscode'; 5 | 6 | import * as path from 'path'; 7 | import * as fs from 'fs'; 8 | import * as cp from 'child_process'; 9 | 10 | import { defaultTasksJson, defaultLaunchJson } from './defaults'; 11 | import { LanguageClient, LanguageClientOptions, ServerOptions } from 'vscode-languageclient/node'; 12 | import { workspace } from "vscode"; 13 | 14 | import { XMLHttpRequest, Document } from 'xmlhttprequest'; 15 | 16 | let channel: vscode.OutputChannel; 17 | 18 | // this method is called when your extension is activated 19 | // your extension is activated the very first time the command is executed 20 | export async function activate(context: vscode.ExtensionContext) { 21 | channel = vscode.window.createOutputChannel("PeachPie"); 22 | channel.appendLine("PeachPie extension was activated."); 23 | 24 | context.subscriptions.push( 25 | channel, 26 | 27 | vscode.commands.registerCommand('peachpie.createconsole', async () => { 28 | await createTemplate("console"); 29 | }), 30 | 31 | vscode.commands.registerCommand('peachpie.createlibrary', async () => { 32 | await createTemplate("library"); 33 | }), 34 | 35 | startLanguageServer(context) 36 | ); 37 | 38 | // 39 | await checkNewsletter(context); 40 | } 41 | 42 | // check for newsletter 43 | async function checkNewsletter(context: vscode.ExtensionContext) { 44 | 45 | const check_key = "last-newsletter-check"; 46 | const check_interval = 1000 * 60 * 60 * 24; // number of milliseconds between two checks // 24h hours 47 | var last_check = context.globalState.get(check_key, -1); // time stamp of the last newsletter check 48 | var now = Date.now(); 49 | 50 | if (now - last_check < check_interval) { 51 | return; 52 | } 53 | 54 | // download RSS feed XML 55 | const rss_feed_url: string = "https://www.peachpie.io/feed"; 56 | let xmlfeed = await new Promise(function (resolve, reject) { 57 | let xhr = new XMLHttpRequest(); 58 | xhr.open("GET", rss_feed_url, true); 59 | xhr.onreadystatechange = function () { 60 | if (this.readyState == xhr.DONE) { 61 | if (this.status == 200) { 62 | // this.responseXML is always "" even when responseType is set :/ 63 | const xmldom = require("xmldom"); 64 | var doc = new xmldom.DOMParser().parseFromString(this.responseText); 65 | resolve(doc); // success -> and succeess message 66 | } else { 67 | resolve(undefined); 68 | } 69 | } 70 | }; 71 | xhr.ontimeout = () => { 72 | resolve(undefined) 73 | }; 74 | xhr.onerror = () => { 75 | resolve(undefined); 76 | }; 77 | xhr.send(null); 78 | }); 79 | 80 | if (!xmlfeed) { 81 | return; 82 | } 83 | 84 | // find article that was not shown yet (): 85 | const xpath = require("xpath"); 86 | let items = xpath.select('//item', xmlfeed); 87 | let article = items.find(function (item) { 88 | let pubDate = xpath.select1("pubDate", item); 89 | if (pubDate && xpath.select1("title", item) && xpath.select1("link", item)) { 90 | let pubDateValue = Date.parse(pubDate.textContent); 91 | if (pubDateValue > last_check) { 92 | return true; 93 | } 94 | } 95 | }); 96 | 97 | // show notification about the article (, <link>) 98 | if (article) { 99 | 100 | // remember we checked for news: 101 | context.globalState.update(check_key, now); 102 | 103 | // show notification: 104 | let title = xpath.select1("title", article).textContent; 105 | let link = xpath.select1("link", article).textContent; 106 | 107 | channel.appendLine(`New article: ${title} at ${link}`) 108 | 109 | const readnow = "Read now" 110 | const dismiss = "Dismiss" 111 | 112 | if (await vscode.window.showInformationMessage(title, readnow/*, dismiss*/) == readnow) { 113 | require("open")(link); 114 | } 115 | } 116 | } 117 | 118 | function startLanguageServer(context: vscode.ExtensionContext): vscode.Disposable { 119 | // TODO: Handle the proper publishing of the executable 120 | let serverPath = context.asAbsolutePath("out/server/Peachpie.LanguageServer.dll"); 121 | let serverOptions: ServerOptions = { 122 | run: { command: "dotnet", args: [serverPath] }, 123 | debug: { command: "dotnet", args: [serverPath, "--debug"] } 124 | } 125 | 126 | // Options to control the language client 127 | let clientOptions: LanguageClientOptions = { 128 | // Register the server for PHP documents 129 | documentSelector: [{ 130 | scheme: 'file', 131 | language: 'php', 132 | }], 133 | synchronize: { 134 | // Notify the server when running dotnet restore on a project in the workspace 135 | fileEvents: [ 136 | workspace.createFileSystemWatcher('**/project.assets.json'), 137 | workspace.createFileSystemWatcher('**/*.msbuildproj') 138 | ] 139 | } 140 | } 141 | 142 | // Create the language client and start the server 143 | return new LanguageClient("PeachPie Language Server", serverOptions, clientOptions).start(); 144 | } 145 | 146 | function showInfo(message: string, doShowWindow = false) { 147 | channel.appendLine(message); 148 | if (doShowWindow) { 149 | vscode.window.showInformationMessage(message); 150 | } 151 | } 152 | 153 | function showError(message: string, doShowWindow = true) { 154 | channel.appendLine(message); 155 | if (doShowWindow) { 156 | vscode.window.showErrorMessage(message); 157 | } 158 | } 159 | 160 | async function createTemplate(templatename: string) { 161 | // We will write successes to the output channel. In case of an error, we will display it also 162 | // in the window and skip the remaining operations. 163 | channel.show(true); 164 | 165 | // Check the opened folder 166 | let rootPath = vscode.workspace.rootPath; 167 | if (rootPath != null) { 168 | showInfo(`Creating PeachPie project in '${rootPath}' ...\n`); 169 | } else { 170 | showError("A folder must be opened in the Explorer panel.\n"); 171 | return; 172 | } 173 | 174 | // const templatename = "console" 175 | const templatefilename = templatename + ".msbuildproj" 176 | 177 | // Create .msbuildproj project file: 178 | let projectPath = path.join(rootPath, templatefilename); 179 | showInfo(`Creating '${templatename}' ...`); 180 | if (fs.existsSync(projectPath)) { 181 | showInfo(`Warning: project file already exists, won't be created.`); 182 | } else { 183 | if (await createProjectFile(projectPath, templatefilename)) { 184 | showInfo("Project file created successfully."); 185 | } else { 186 | showError("Error in creating project file.\n"); 187 | return; 188 | } 189 | } 190 | 191 | // Create or update .tasks.json and .launch.json 192 | showInfo("Configuring build and debugging in 'tasks.json' and 'launch.json' ..."); 193 | let isTasksSuccess = 194 | await configureTasks() && 195 | (templatename != "console" || await configureLaunch()); 196 | 197 | if (isTasksSuccess) { 198 | showInfo("Build tasks successfully configured.\n"); 199 | } else { 200 | showError("Error in configuring the build tasks.\n"); 201 | return; 202 | } 203 | 204 | // Run dotnet restore 205 | let isError = false; 206 | showInfo("Running dotnet restore to install PeachPie Sdk ..."); 207 | await execChildProcess("dotnet restore", rootPath) 208 | .then((data: string) => { 209 | showInfo(data); 210 | if (data.includes("Restore completed in")) { 211 | showInfo("Project dependencies were successfully installed.\n"); 212 | } else { 213 | showError("Error in installing project dependencies.\n"); 214 | isError = true; 215 | } 216 | }) 217 | .catch((error) => { 218 | showError("For building and executing, PeachPie needs .NET Core CLI tools to be available on the path. Make sure they are installed properly.\n"); 219 | isError = true; 220 | }); 221 | if (isError) { 222 | return; 223 | } 224 | 225 | // Activate Omnisharp C# extension for debugging 226 | let csharpExtension = vscode.extensions.getExtension("ms-vscode.csharp"); 227 | if (csharpExtension == null) { 228 | showError("Install OmniSharp C# extension in order to enable the debugging of PeachPie projects.\n"); 229 | return; 230 | } else { 231 | if (csharpExtension.isActive) { 232 | showInfo("OmniSharp C# extension is already active.\n"); 233 | } else { 234 | showInfo("Activating OmniSharp C# extension to take care of the project structure and debugging ...\n"); 235 | await csharpExtension.activate(); 236 | } 237 | showInfo("PeachPie project was successfully configured.", true); 238 | } 239 | } 240 | 241 | // Create project file in the opened root folder 242 | async function createProjectFile(filePath: string, templateFile: string): Promise<boolean> { 243 | let projectUri = vscode.Uri.parse(`untitled:${filePath}`); 244 | let projectDocument = await vscode.workspace.openTextDocument(projectUri); 245 | let extensionDir = vscode.extensions.getExtension("iolevel.peachpie-vscode").extensionPath; 246 | let projectContent = fs.readFileSync(extensionDir + "/templates/" + templateFile).toString(); 247 | let projectEdit = vscode.TextEdit.insert(new vscode.Position(0, 0), projectContent); 248 | 249 | let wsEdit = new vscode.WorkspaceEdit(); 250 | wsEdit.set(projectUri, [projectEdit]); 251 | let isSuccess = await vscode.workspace.applyEdit(wsEdit); 252 | 253 | if (isSuccess) { 254 | isSuccess = await vscode.workspace.saveAll(true); 255 | } 256 | 257 | return isSuccess; 258 | } 259 | 260 | // Overwrite tasks configuration, resulting in adding or replacing .vscode/tasks.json 261 | async function configureTasks(): Promise<boolean> { 262 | return overwriteConfiguration("tasks", defaultTasksJson); 263 | } 264 | 265 | // Overwrite launch configuration, resulting in adding or replacing .vscode/tasks.json 266 | async function configureLaunch(): Promise<boolean> { 267 | return overwriteConfiguration("launch", defaultLaunchJson); 268 | } 269 | 270 | async function overwriteConfiguration(section: string, configuration: any): Promise<boolean> { 271 | let tasksConfig = vscode.workspace.getConfiguration(section); 272 | if (tasksConfig == null) { 273 | channel.appendLine(`Unable to load ${section} configuration`); 274 | return false; 275 | } 276 | 277 | try { 278 | for (var key in configuration) { 279 | if (configuration.hasOwnProperty(key)) { 280 | var element = configuration[key]; 281 | 282 | // Not defined in the Typescript interface, therefore called this way 283 | await tasksConfig['update'].call(tasksConfig, key, element); 284 | } 285 | } 286 | } catch (error) { 287 | channel.appendLine("Error in configuring the build tasks: " + (<Error>error).message); 288 | return false; 289 | } 290 | 291 | return true; 292 | } 293 | 294 | // Taken from omnisharp-vscode 295 | function execChildProcess(command: string, workingDirectory: string): Promise<string> { 296 | return new Promise<string>((resolve, reject) => { 297 | cp.exec(command, { cwd: workingDirectory, maxBuffer: 500 * 1024 }, (error, stdout, stderr) => { 298 | if (error) { 299 | reject(error); 300 | } 301 | else if (stderr && stderr.length > 0) { 302 | reject(new Error(stderr)); 303 | } 304 | else { 305 | resolve(stdout); 306 | } 307 | }); 308 | }); 309 | } 310 | 311 | // this method is called when your extension is deactivated 312 | export function deactivate() { 313 | } -------------------------------------------------------------------------------- /src/Peachpie.VSCode/templates/console.msbuildproj: -------------------------------------------------------------------------------- 1 | <Project Sdk="Peachpie.NET.Sdk/1.0.23"> 2 | 3 | <PropertyGroup> 4 | <OutputType>exe</OutputType> 5 | <TargetFramework>net6.0</TargetFramework> 6 | </PropertyGroup> 7 | 8 | <ItemGroup> 9 | <Compile Include="**/*.php" /> 10 | </ItemGroup> 11 | 12 | </Project> -------------------------------------------------------------------------------- /src/Peachpie.VSCode/templates/library.msbuildproj: -------------------------------------------------------------------------------- 1 | <Project Sdk="Peachpie.NET.Sdk/1.0.23"> 2 | 3 | <PropertyGroup> 4 | <OutputType>library</OutputType> 5 | <TargetFramework>netstandard2.1</TargetFramework> 6 | <GenerateDocumentationFile>true</GenerateDocumentationFile> 7 | <GeneratePackageOnBuild>true</GeneratePackageOnBuild> 8 | </PropertyGroup> 9 | 10 | <ItemGroup> 11 | <Compile Include="**/*.php" /> 12 | </ItemGroup> 13 | 14 | </Project> -------------------------------------------------------------------------------- /src/Peachpie.VSCode/test/extension.test.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Note: This example test is leveraging the Mocha test framework. 3 | // Please refer to their documentation on https://mochajs.org/ for help. 4 | // 5 | 6 | // The module 'assert' provides assertion methods from node 7 | import * as assert from 'assert'; 8 | 9 | // You can import and use all API from the 'vscode' module 10 | // as well as import your extension to test it 11 | import * as vscode from 'vscode'; 12 | import * as myExtension from '../src/extension'; 13 | 14 | // Defines a Mocha test suite to group tests of similar kind together 15 | suite("Extension Tests", () => { 16 | 17 | // Defines a Mocha unit test 18 | test("Something 1", () => { 19 | assert.equal(-1, [1, 2, 3].indexOf(5)); 20 | assert.equal(-1, [1, 2, 3].indexOf(0)); 21 | }); 22 | }); -------------------------------------------------------------------------------- /src/Peachpie.VSCode/test/index.ts: -------------------------------------------------------------------------------- 1 | // 2 | // PLEASE DO NOT MODIFY / DELETE UNLESS YOU KNOW WHAT YOU ARE DOING 3 | // 4 | // This file is providing the test runner to use when running extension tests. 5 | // By default the test runner in use is Mocha based. 6 | // 7 | // You can provide your own test runner if you want to override it by exporting 8 | // a function run(testRoot: string, clb: (error:Error) => void) that the extension 9 | // host can call to run the tests. The test runner is expected to use console.log 10 | // to report the results back to the caller. When the tests are finished, return 11 | // a possible error to the callback or null if none. 12 | 13 | var testRunner = require('vscode/lib/testrunner'); 14 | 15 | // You can directly control Mocha options by uncommenting the following lines 16 | // See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options for more info 17 | testRunner.configure({ 18 | ui: 'tdd', // the TDD UI is being used in extension.test.ts (suite, test, etc.) 19 | useColors: true // colored output from test results 20 | }); 21 | 22 | module.exports = testRunner; -------------------------------------------------------------------------------- /src/Peachpie.VSCode/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "outDir": "out", 6 | "esModuleInterop": true, 7 | "lib": [ 8 | "es6", 9 | "ES2021.String" 10 | ], 11 | "skipLibCheck": true, 12 | "sourceMap": true, 13 | "rootDir": "." 14 | }, 15 | "exclude": [ 16 | "node_modules", 17 | ".vscode-test" 18 | ] 19 | } -------------------------------------------------------------------------------- /src/Peachpie.VSCode/vsc-extension-quickstart.md: -------------------------------------------------------------------------------- 1 | # Welcome to your first VS Code Extension 2 | 3 | ## What's in the folder 4 | * This folder contains all of the files necessary for your extension 5 | * `package.json` - this is the manifest file in which you declare your extension and command. 6 | The sample plugin registers a command and defines its title and command name. With this information 7 | VS Code can show the command in the command palette. It doesn’t yet need to load the plugin. 8 | * `src/extension.ts` - this is the main file where you will provide the implementation of your command. 9 | The file exports one function, `activate`, which is called the very first time your extension is 10 | activated (in this case by executing the command). Inside the `activate` function we call `registerCommand`. 11 | We pass the function containing the implementation of the command as the second parameter to 12 | `registerCommand`. 13 | 14 | ## Get up and running straight away 15 | * press `F5` to open a new window with your extension loaded 16 | * run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Hello World` 17 | * set breakpoints in your code inside `src/extension.ts` to debug your extension 18 | * find output from your extension in the debug console 19 | 20 | ## Make changes 21 | * you can relaunch the extension from the debug toolbar after changing code in `src/extension.ts` 22 | * you can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes 23 | 24 | ## Explore the API 25 | * you can open the full set of our API when you open the file `node_modules/vscode/vscode.d.ts` 26 | 27 | ## Run tests 28 | * open the debug viewlet (`Ctrl+Shift+D` or `Cmd+Shift+D` on Mac) and from the launch configuration dropdown pick `Launch Tests` 29 | * press `F5` to run the tests in a new window with your extension loaded 30 | * see the output of the test result in the debug console 31 | * make changes to `test/extension.test.ts` or create new test files inside the `test` folder 32 | * by convention, the test runner will only consider files matching the name pattern `**.test.ts` 33 | * you can create folders inside the `test` folder to structure your tests any way you want --------------------------------------------------------------------------------