├── .gitattributes ├── .github └── workflows │ └── release.yml ├── .gitignore ├── GraphQLTools.Core ├── GraphQLTools.Core.csproj ├── Properties │ └── AssemblyInfo.cs ├── Syntax │ ├── DiagnosticSpan.cs │ ├── ErrorMessages.cs │ ├── GqlLexer.cs │ ├── GqlParser.cs │ ├── ISyntaxSpanCollection.cs │ ├── SourceText.cs │ ├── SyntaxKind.cs │ ├── SyntaxSpan.cs │ ├── TextFacts.cs │ └── TokenKind.cs └── Utils │ └── RoslynExtensions.cs ├── GraphQLTools.Tests ├── GraphQLTools.Tests.csproj ├── Properties │ └── AssemblyInfo.cs ├── Syntax │ └── GqlParserTests.cs └── packages.config ├── GraphQLTools.sln ├── GraphQLTools ├── Analysis │ ├── AnalyzedSpan.cs │ ├── AnalyzedSpanBag.cs │ ├── RawStringLiteralAnalyzedSpan.cs │ ├── SimpleStringLiteralAnalyzedSpan.cs │ ├── SnapshotSourceText.cs │ ├── SyntaxSpanList.cs │ ├── SyntaxSpanListPooledObjectPolicy.cs │ ├── SyntaxSpanListSlice.cs │ └── VerbatimStringLiteralAnalyzedSpan.cs ├── Classification │ ├── GqlClassificationTypes.cs │ ├── GqlClassifierClassificationDefinition.cs │ └── GqlClassifierFormats.cs ├── GraphQLTools.csproj ├── GraphQLToolsPackage.cs ├── LICENSE.txt ├── Properties │ └── AssemblyInfo.cs ├── Resources │ └── logo.png ├── Tagging │ ├── GqlTagSpanFactory.cs │ ├── GqlTagger.cs │ ├── GqlTaggerProvider.cs │ └── TaggerCancellationTokenSource.cs └── source.extension.vsixmanifest ├── LICENSE ├── README.md ├── images └── syntax-highlighting.png └── publishManifest.json /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | release: 5 | branches: 6 | - main 7 | tags: 8 | - '*' 9 | types: 10 | - published 11 | 12 | jobs: 13 | release: 14 | runs-on: windows-latest 15 | steps: 16 | - uses: actions/checkout@v2 17 | 18 | - name: Setup MSBuild 19 | uses: microsoft/setup-msbuild@v1 20 | 21 | - name: Setup NuGet 22 | uses: NuGet/setup-nuget@v1.2.0 23 | 24 | - name: Restore Packages 25 | run: nuget restore GraphQLTools.sln 26 | 27 | - name: Build Project 28 | run: msbuild.exe ./GraphQLTools/GraphQLTools.csproj /p:configuration="Release" 29 | 30 | - name: Publish extension to Marketplace 31 | uses: cezarypiatek/VsixPublisherAction@1.0 32 | with: 33 | extension-file: ./GraphQLTools/bin/Release/GraphQLTools.vsix 34 | publish-manifest-file: ./publishManifest.json 35 | personal-access-code: ${{ secrets.MARKETPLACE_PAT }} 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Ll]og/ 33 | [Ll]ogs/ 34 | 35 | # Visual Studio 2015/2017 cache/options directory 36 | .vs/ 37 | # Uncomment if you have tasks that create the project's static files in wwwroot 38 | #wwwroot/ 39 | 40 | # Visual Studio 2017 auto generated files 41 | Generated\ Files/ 42 | 43 | # MSTest test Results 44 | [Tt]est[Rr]esult*/ 45 | [Bb]uild[Ll]og.* 46 | 47 | # NUnit 48 | *.VisualState.xml 49 | TestResult.xml 50 | nunit-*.xml 51 | 52 | # Build Results of an ATL Project 53 | [Dd]ebugPS/ 54 | [Rr]eleasePS/ 55 | dlldata.c 56 | 57 | # Benchmark Results 58 | BenchmarkDotNet.Artifacts/ 59 | 60 | # .NET Core 61 | project.lock.json 62 | project.fragment.lock.json 63 | artifacts/ 64 | 65 | # ASP.NET Scaffolding 66 | ScaffoldingReadMe.txt 67 | 68 | # StyleCop 69 | StyleCopReport.xml 70 | 71 | # Files built by Visual Studio 72 | *_i.c 73 | *_p.c 74 | *_h.h 75 | *.ilk 76 | *.meta 77 | *.obj 78 | *.iobj 79 | *.pch 80 | *.pdb 81 | *.ipdb 82 | *.pgc 83 | *.pgd 84 | *.rsp 85 | *.sbr 86 | *.tlb 87 | *.tli 88 | *.tlh 89 | *.tmp 90 | *.tmp_proj 91 | *_wpftmp.csproj 92 | *.log 93 | *.tlog 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio 6 auto-generated project file (contains which files were open etc.) 298 | *.vbp 299 | 300 | # Visual Studio 6 workspace and project file (working project files containing files to include in project) 301 | *.dsw 302 | *.dsp 303 | 304 | # Visual Studio 6 technical files 305 | *.ncb 306 | *.aps 307 | 308 | # Visual Studio LightSwitch build output 309 | **/*.HTMLClient/GeneratedArtifacts 310 | **/*.DesktopClient/GeneratedArtifacts 311 | **/*.DesktopClient/ModelManifest.xml 312 | **/*.Server/GeneratedArtifacts 313 | **/*.Server/ModelManifest.xml 314 | _Pvt_Extensions 315 | 316 | # Paket dependency manager 317 | .paket/paket.exe 318 | paket-files/ 319 | 320 | # FAKE - F# Make 321 | .fake/ 322 | 323 | # CodeRush personal settings 324 | .cr/personal 325 | 326 | # Python Tools for Visual Studio (PTVS) 327 | __pycache__/ 328 | *.pyc 329 | 330 | # Cake - Uncomment if you are using it 331 | # tools/** 332 | # !tools/packages.config 333 | 334 | # Tabs Studio 335 | *.tss 336 | 337 | # Telerik's JustMock configuration file 338 | *.jmconfig 339 | 340 | # BizTalk build output 341 | *.btp.cs 342 | *.btm.cs 343 | *.odx.cs 344 | *.xsd.cs 345 | 346 | # OpenCover UI analysis results 347 | OpenCover/ 348 | 349 | # Azure Stream Analytics local run output 350 | ASALocalRun/ 351 | 352 | # MSBuild Binary and Structured Log 353 | *.binlog 354 | 355 | # NVidia Nsight GPU debugger configuration file 356 | *.nvuser 357 | 358 | # MFractors (Xamarin productivity tool) working folder 359 | .mfractor/ 360 | 361 | # Local History for Visual Studio 362 | .localhistory/ 363 | 364 | # Visual Studio History (VSHistory) files 365 | .vshistory/ 366 | 367 | # BeatPulse healthcheck temp database 368 | healthchecksdb 369 | 370 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 371 | MigrationBackup/ 372 | 373 | # Ionide (cross platform F# VS Code tools) working folder 374 | .ionide/ 375 | 376 | # Fody - auto-generated XML schema 377 | FodyWeavers.xsd 378 | 379 | # VS Code files for those working on multiple tools 380 | .vscode/* 381 | !.vscode/settings.json 382 | !.vscode/tasks.json 383 | !.vscode/launch.json 384 | !.vscode/extensions.json 385 | *.code-workspace 386 | 387 | # Local History for Visual Studio Code 388 | .history/ 389 | 390 | # Windows Installer files from build outputs 391 | *.cab 392 | *.msi 393 | *.msix 394 | *.msm 395 | *.msp 396 | 397 | # JetBrains Rider 398 | *.sln.iml 399 | 400 | # EditorConfig 401 | .editorconfig -------------------------------------------------------------------------------- /GraphQLTools.Core/GraphQLTools.Core.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | latest 6 | enable 7 | enable 8 | nullable 9 | $(MSBuildProjectName.Replace(" ", "_").Replace(".Core", "")) 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /GraphQLTools.Core/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("GraphQLTools")] 4 | [assembly: InternalsVisibleTo("GraphQLTools.Tests")] 5 | [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] 6 | -------------------------------------------------------------------------------- /GraphQLTools.Core/Syntax/DiagnosticSpan.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis.Text; 2 | 3 | namespace GraphQLTools.Syntax; 4 | 5 | internal readonly struct DiagnosticSpan 6 | { 7 | private DiagnosticSpan(string message, int start, int length) 8 | { 9 | Message = message; 10 | Start = start; 11 | Length = length; 12 | } 13 | 14 | public string Message { get; } 15 | 16 | public int Start { get; } 17 | 18 | public int Length { get; } 19 | 20 | public int End => Start + Length; 21 | 22 | public DiagnosticSpan Shift(int offset) => new DiagnosticSpan(Message, Start + offset, Length); 23 | 24 | public static DiagnosticSpan Create(string message, TextSpan span) => new DiagnosticSpan(message, span.Start, span.Length); 25 | } 26 | -------------------------------------------------------------------------------- /GraphQLTools.Core/Syntax/ErrorMessages.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQLTools.Syntax; 2 | 3 | internal static class ErrorMessages 4 | { 5 | private const string s_prefix = "GraphQL syntax error - "; 6 | 7 | public const string UnterminatedString = s_prefix + "Unterminated string."; 8 | public const string InvalidString = s_prefix + "Invalid string."; 9 | public const string InvalidNumber = s_prefix + "Invalid number."; 10 | public const string ExpectedName = s_prefix + "Expected name."; 11 | public const string ExpectedDefinition = s_prefix + "Expected definition."; 12 | public const string ExpectedSelectionSet = s_prefix + "Expected selection set."; 13 | public const string ExpectedSelection = s_prefix + "Expected selection."; 14 | public const string ExpectedRightParenthesis = s_prefix + "Expected ')'."; 15 | public const string ExpectedRightBracket = s_prefix + "Expected ']'."; 16 | public const string ExpectedRightBrace = s_prefix + "Expected '}'"; 17 | public const string ExpectedType = s_prefix + "Expected type."; 18 | public const string ExpectedTypeName = s_prefix + "Expected type name."; 19 | public const string ExpectedFragment = s_prefix + "Expected fragment."; 20 | public const string ExpectedArgument = s_prefix + "Expected argument."; 21 | public const string ExpectedColon = s_prefix + "Expected ':'."; 22 | public const string ExpectedValue = s_prefix + "Expected value."; 23 | public const string ExpectedVariableDefinition = s_prefix + "Expected variable definition."; 24 | public const string ExpectedFragmentName = s_prefix + "Expected fragment name."; 25 | public const string ExpectedTypeCondition = s_prefix + "Expected type condition."; 26 | public const string AnonymousOperationError = s_prefix + "An anonymous operation must be the only defined operation."; 27 | 28 | public static string UnexpectedCharacter(char c) 29 | { 30 | return char.IsControl(c) 31 | ? s_prefix + "Unexpected character." 32 | : s_prefix + $"Unexpected character: '{c}'."; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /GraphQLTools.Core/Syntax/GqlLexer.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis.Text; 2 | using System.Runtime.CompilerServices; 3 | using static GraphQLTools.Syntax.TextFacts; // PERF - this must be compiled with at least C# 11 because of method group usage. 4 | 5 | namespace GraphQLTools.Syntax; 6 | 7 | internal ref struct GqlLexer 8 | { 9 | private readonly SourceText _source; 10 | private int _spanStart; 11 | 12 | public GqlLexer(SourceText source) 13 | { 14 | _source = source; 15 | _spanStart = source.Position; 16 | Kind = TokenKind.StartOfFile; 17 | ErrorMessage = null; 18 | } 19 | 20 | public TokenKind Kind { readonly get; private set; } 21 | 22 | public string? ErrorMessage { readonly get; private set; } 23 | 24 | public readonly TextSpan Span => new TextSpan(_spanStart, Length); 25 | 26 | private readonly int Length => _source.Position - _spanStart; 27 | 28 | public readonly TokenKind Lookahead() 29 | { 30 | GqlLexer lexer = this; 31 | var checkpoint = _source.Checkpoint(); 32 | 33 | try 34 | { 35 | lexer.MoveNext(); 36 | return lexer.Kind; 37 | } 38 | finally 39 | { 40 | checkpoint.Reset(); 41 | } 42 | } 43 | 44 | public readonly bool ValueMatch(string expected) 45 | { 46 | if (Length != expected.Length) 47 | return false; 48 | 49 | return _source.Match(expected); 50 | } 51 | 52 | public bool MoveNext() 53 | { 54 | if (Kind == TokenKind.EndOfFile) 55 | return false; 56 | 57 | _spanStart = _source.Position; 58 | ErrorMessage = null; 59 | 60 | if (!_source.MoveNext()) 61 | { 62 | Kind = TokenKind.EndOfFile; 63 | return true; 64 | } 65 | 66 | char current = _source.Current; 67 | 68 | if (IsAlpha(current)) 69 | return Name(); 70 | 71 | return current switch 72 | { 73 | '{' => Token(TokenKind.LeftBrace), 74 | '}' => Token(TokenKind.RightBrace), 75 | '"' => _source.Eat("\"\"") ? BlockString() : String(), 76 | ' ' or '\t' or '\n' or '\r' => Ignored(), 77 | ',' => Token(TokenKind.Comma), 78 | >= '1' and <= '9' => NonZeroDigit(), 79 | '-' => MinusSign(), 80 | '0' => Zero(), 81 | '(' => Token(TokenKind.LeftParenthesis), 82 | ')' => Token(TokenKind.RightParenthesis), 83 | '[' => Token(TokenKind.LeftBracket), 84 | ']' => Token(TokenKind.RightBracket), 85 | ':' => Token(TokenKind.Colon), 86 | '=' => Token(TokenKind.Equals), 87 | '$' => VariableName(), 88 | '@' => DirectiveName(), 89 | '.' when _source.Eat("..") => Token(TokenKind.Spread), 90 | '|' => Token(TokenKind.Pipe), 91 | '!' => Token(TokenKind.Bang), 92 | '?' => Token(TokenKind.QuestionMark), 93 | '&' => Token(TokenKind.Ampersand), 94 | '#' => Comment(), 95 | SourceText.InvalidChar => Token(TokenKind.BadSource), 96 | _ => Unexpected(current) 97 | }; 98 | } 99 | 100 | private bool Name() 101 | { 102 | _source.EatWhile(IsAlphaNumeric); 103 | 104 | return Token(TokenKind.Name); 105 | } 106 | 107 | private bool VariableName() 108 | { 109 | if (!_source.Eat(IsAlpha)) 110 | { 111 | _source.EatWhile(IsWordLike); 112 | return Error(ErrorMessages.ExpectedName); 113 | } 114 | 115 | _source.EatWhile(IsAlphaNumeric); 116 | 117 | return Token(TokenKind.VariableName); 118 | } 119 | 120 | private bool DirectiveName() 121 | { 122 | if (!_source.Eat(IsAlpha)) 123 | { 124 | _source.EatWhile(IsWordLike); 125 | return Error(ErrorMessages.ExpectedName); 126 | } 127 | 128 | _source.EatWhile(IsAlphaNumeric); 129 | 130 | return Token(TokenKind.DirectiveName); 131 | } 132 | 133 | private bool String() 134 | { 135 | bool isValid = true; 136 | 137 | while (_source.MoveNext()) 138 | { 139 | char current = _source.Current; 140 | 141 | if (IsNewlineCharacter(current)) 142 | { 143 | return Error(ErrorMessages.UnterminatedString); 144 | } 145 | 146 | if (current == '"') 147 | { 148 | return isValid 149 | ? Token(TokenKind.String) 150 | : Error(ErrorMessages.InvalidString); 151 | } 152 | 153 | if (current == '\\') 154 | { 155 | isValid &= EatEscapeSequence(); 156 | } 157 | } 158 | 159 | return Error(ErrorMessages.UnterminatedString); 160 | } 161 | 162 | private readonly bool EatEscapeSequence() 163 | { 164 | if (_source.Eat('u')) 165 | return EatUnicodeSequence(); 166 | 167 | return _source.Eat(IsEscaped); 168 | } 169 | 170 | private readonly bool EatUnicodeSequence() 171 | { 172 | for (int i = 0; i < 4; i++) 173 | { 174 | if (!_source.Eat(IsHexDigit)) 175 | return false; 176 | } 177 | 178 | return true; 179 | } 180 | 181 | private bool BlockString() 182 | { 183 | while (_source.MoveNext()) 184 | { 185 | char current = _source.Current; 186 | 187 | if (current == '"' && _source.Eat("\"\"")) 188 | return Token(TokenKind.BlockString); 189 | 190 | if (current == '\\') 191 | { 192 | _source.Eat("\"\""); 193 | } 194 | } 195 | 196 | return Error(ErrorMessages.UnterminatedString); 197 | } 198 | 199 | private bool NonZeroDigit() 200 | { 201 | _source.EatWhile(IsDigit); 202 | 203 | TokenKind kind = CheckFloat(); 204 | 205 | if (_source.EatWhile(IsWordLike)) 206 | return Error(ErrorMessages.InvalidNumber); 207 | 208 | return kind == TokenKind.Error 209 | ? Error(ErrorMessages.InvalidNumber) 210 | : Token(kind); 211 | } 212 | 213 | private bool MinusSign() 214 | { 215 | while (_source.MoveNext()) 216 | { 217 | char current = _source.Current; 218 | 219 | if (current == '0') 220 | return Zero(); 221 | 222 | if (IsDigit(current)) 223 | return NonZeroDigit(); 224 | 225 | _source.EatWhile(IsWordLike); 226 | } 227 | 228 | return Error(ErrorMessages.InvalidNumber); 229 | } 230 | 231 | private bool Zero() 232 | { 233 | TokenKind kind = CheckFloat(); 234 | 235 | if (kind == TokenKind.Integer && _source.EatWhile(IsDigit)) 236 | return Error(ErrorMessages.InvalidNumber); 237 | 238 | if (_source.EatWhile(IsWordLike)) 239 | return Error(ErrorMessages.InvalidNumber); 240 | 241 | return kind == TokenKind.Error 242 | ? Error(ErrorMessages.InvalidNumber) 243 | : Token(kind); 244 | } 245 | 246 | private bool Ignored() 247 | { 248 | _source.EatWhile(IsIgnored); 249 | 250 | return MoveNext(); 251 | } 252 | 253 | private bool Comment() 254 | { 255 | _source.EatUntil(IsNewlineCharacter); 256 | 257 | return Token(TokenKind.Comment); 258 | } 259 | 260 | private bool Unexpected(char current) 261 | { 262 | return Error(ErrorMessages.UnexpectedCharacter(current)); 263 | } 264 | 265 | private readonly TokenKind CheckFloat() 266 | { 267 | TokenKind kind = TokenKind.Integer; 268 | 269 | if (_source.Eat('.')) 270 | { 271 | kind = TokenKind.Float; 272 | 273 | if (!_source.EatWhile(IsDigit)) 274 | return TokenKind.Error; 275 | } 276 | 277 | if (_source.Eat(IsExponentIndicator)) 278 | { 279 | kind = TokenKind.Float; 280 | 281 | _source.Eat(IsSign); 282 | 283 | if (!_source.EatWhile(IsDigit)) 284 | return TokenKind.Error; 285 | } 286 | 287 | return kind; 288 | } 289 | 290 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 291 | private bool Token(TokenKind kind) 292 | { 293 | Kind = kind; 294 | return true; 295 | } 296 | 297 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 298 | private bool Error(string message) 299 | { 300 | Kind = TokenKind.Error; 301 | ErrorMessage = message; 302 | return true; 303 | } 304 | } 305 | -------------------------------------------------------------------------------- /GraphQLTools.Core/Syntax/GqlParser.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis.Text; 2 | using System.Diagnostics; 3 | 4 | namespace GraphQLTools.Syntax; 5 | 6 | internal ref struct GqlParser 7 | { 8 | private static readonly string[] s_operationTypes = new string[] { "query", "mutation", "subscription" }; 9 | private static readonly string[] s_literals = new string[] { "true", "false", "null" }; 10 | private static readonly string[] s_onKeyword = new string[] { "on" }; 11 | private static readonly string[] s_fragmentKeyword = new string[] { "fragment" }; 12 | 13 | private readonly ISyntaxSpanCollection _spans; 14 | private GqlLexer _lexer; 15 | private TextSpan _anonymousOperationSpan; 16 | private bool _hasOperations; 17 | 18 | private GqlParser(ISyntaxSpanCollection spans, GqlLexer lexer) 19 | { 20 | _spans = spans; 21 | _lexer = lexer; 22 | _anonymousOperationSpan = default; 23 | _hasOperations = false; 24 | } 25 | 26 | private void Parse() 27 | { 28 | if (!_lexer.MoveNext()) 29 | return; 30 | 31 | try 32 | { 33 | ParseDocument(); 34 | } 35 | catch (DiagnosticException exception) 36 | { 37 | TextSpan span = _lexer.Span; 38 | if (span.IsEmpty) 39 | { 40 | span = new TextSpan(span.Start, 1); 41 | } 42 | else if (_lexer.Kind != TokenKind.EndOfFile) 43 | { 44 | _spans.AddError(span); 45 | } 46 | 47 | _spans.SetDiagnostic(span, exception.Message); 48 | Panic(); 49 | } 50 | catch (AnonymousOperationException exception) 51 | { 52 | _spans.SetDiagnostic(_anonymousOperationSpan, exception.Message); 53 | Panic(); 54 | } 55 | catch (BadSourceException) 56 | { 57 | Panic(); 58 | } 59 | } 60 | 61 | private void Panic() 62 | { 63 | while (_lexer.MoveNext()) 64 | { 65 | switch (_lexer.Kind) 66 | { 67 | case TokenKind.Error: 68 | _spans.AddError(_lexer.Span); 69 | break; 70 | 71 | case TokenKind.BadSource: 72 | break; 73 | 74 | case TokenKind.Comma: 75 | case TokenKind.Bang: 76 | case TokenKind.QuestionMark: 77 | case TokenKind.Ampersand: 78 | case TokenKind.LeftParenthesis: 79 | case TokenKind.RightParenthesis: 80 | case TokenKind.LeftBracket: 81 | case TokenKind.RightBracket: 82 | case TokenKind.LeftBrace: 83 | case TokenKind.RightBrace: 84 | case TokenKind.Spread: 85 | case TokenKind.Colon: 86 | case TokenKind.Equals: 87 | case TokenKind.Pipe: 88 | _spans.AddPunctuator(_lexer.Span); 89 | break; 90 | 91 | case TokenKind.Name: 92 | _spans.AddName(_lexer.Span); 93 | break; 94 | 95 | case TokenKind.VariableName: 96 | _spans.AddVariableName(_lexer.Span); 97 | break; 98 | 99 | case TokenKind.DirectiveName: 100 | _spans.AddDirectiveName(_lexer.Span); 101 | break; 102 | 103 | case TokenKind.String: 104 | case TokenKind.BlockString: 105 | _spans.AddString(_lexer.Span); 106 | break; 107 | 108 | case TokenKind.Integer: 109 | case TokenKind.Float: 110 | _spans.AddNumber(_lexer.Span); 111 | break; 112 | 113 | case TokenKind.Comment: 114 | _spans.AddComment(_lexer.Span); 115 | break; 116 | } 117 | } 118 | } 119 | 120 | private void ParseDocument() 121 | { 122 | if (!ParseDefinition()) 123 | throw new DiagnosticException(ErrorMessages.ExpectedDefinition); 124 | 125 | while (ParseDefinition()); 126 | 127 | if (_lexer.Kind != TokenKind.EndOfFile) 128 | throw new DiagnosticException(ErrorMessages.ExpectedDefinition); 129 | } 130 | 131 | private bool ParseDefinition() 132 | { 133 | return ParseOperationDefinition() || ParseFragmentDefinition(); 134 | } 135 | 136 | private bool ParseOperationDefinition() 137 | { 138 | if (ParseNamedOperationDefinition() || ParseAnonymousOperationDefinition()) 139 | { 140 | _hasOperations = true; 141 | return true; 142 | } 143 | 144 | return false; 145 | } 146 | 147 | private bool ParseAnonymousOperationDefinition() 148 | { 149 | TextSpan currentSpan = _lexer.Span; 150 | 151 | if (ParseSelectionSet()) 152 | { 153 | _anonymousOperationSpan = currentSpan; 154 | 155 | if (_hasOperations) 156 | throw new AnonymousOperationException(); 157 | 158 | return true; 159 | } 160 | 161 | return false; 162 | } 163 | 164 | private bool ParseNamedOperationDefinition() 165 | { 166 | if (!ConsumeOperationType()) 167 | return false; 168 | 169 | ConsumeOperationName(); 170 | 171 | ParseVariableDefinitions(); 172 | 173 | ParseDirectives(); 174 | 175 | if (!ParseSelectionSet()) 176 | throw new DiagnosticException(ErrorMessages.ExpectedSelectionSet); 177 | 178 | if (_anonymousOperationSpan != default) 179 | throw new AnonymousOperationException(); 180 | 181 | return true; 182 | } 183 | 184 | private bool ParseSelectionSet() 185 | { 186 | if (!ConsumePunctuator(TokenKind.LeftBrace)) 187 | return false; 188 | 189 | if (!ParseSelection()) 190 | throw new DiagnosticException(ErrorMessages.ExpectedSelection); 191 | 192 | while (ParseSelection()); 193 | 194 | if (!ConsumePunctuator(TokenKind.RightBrace)) 195 | throw new DiagnosticException(ErrorMessages.ExpectedRightBrace); 196 | 197 | return true; 198 | } 199 | 200 | private bool ParseSelection() 201 | { 202 | return ParseField() || ParseFragmentSpreadOrInlineFragment(); 203 | } 204 | 205 | private bool ParseField() 206 | { 207 | bool hasAlias = ConsumeFieldAlias(); 208 | 209 | if (!ConsumeFieldName(hasAlias)) 210 | { 211 | if (hasAlias) 212 | throw new DiagnosticException(ErrorMessages.ExpectedName); 213 | 214 | return false; 215 | } 216 | 217 | ParseArguments(); 218 | 219 | ParseDirectives(); 220 | 221 | ParseSelectionSet(); 222 | 223 | return true; 224 | } 225 | 226 | private bool ParseDirectives() 227 | { 228 | if (!ParseDirective()) 229 | return false; 230 | 231 | while (ParseDirective()); 232 | 233 | return true; 234 | } 235 | 236 | private bool ParseDirective() 237 | { 238 | if (!ConsumeDirectiveName()) 239 | return false; 240 | 241 | ParseArguments(); 242 | 243 | return true; 244 | } 245 | 246 | private bool ParseFragmentSpreadOrInlineFragment() 247 | { 248 | if (!ConsumePunctuator(TokenKind.Spread)) 249 | return false; 250 | 251 | if (!ParseFragmentSpread() && !ParseInlineFragment()) 252 | throw new DiagnosticException(ErrorMessages.ExpectedFragment); 253 | 254 | return true; 255 | } 256 | 257 | private bool ParseFragmentSpread() 258 | { 259 | if (!ConsumeFragmentName()) 260 | return false; 261 | 262 | ParseDirectives(); 263 | 264 | return true; 265 | } 266 | 267 | private bool ParseInlineFragment() 268 | { 269 | ParseTypeCondition(); 270 | 271 | ParseDirectives(); 272 | 273 | if (!ParseSelectionSet()) 274 | throw new DiagnosticException(ErrorMessages.ExpectedSelectionSet); 275 | 276 | return true; 277 | } 278 | 279 | private bool ParseTypeCondition() 280 | { 281 | if (!ConsumeOnKeyword()) 282 | return false; 283 | 284 | if (!ConsumeNamedType()) 285 | throw new DiagnosticException(ErrorMessages.ExpectedTypeName); 286 | 287 | return true; 288 | } 289 | 290 | private bool ParseArguments() 291 | { 292 | if (!ConsumePunctuator(TokenKind.LeftParenthesis)) 293 | return false; 294 | 295 | if (!ParseArgument()) 296 | throw new DiagnosticException(ErrorMessages.ExpectedArgument); 297 | 298 | while (ParseArgument()); 299 | 300 | if (!ConsumePunctuator(TokenKind.RightParenthesis)) 301 | throw new DiagnosticException(ErrorMessages.ExpectedRightParenthesis); 302 | 303 | return true; 304 | } 305 | 306 | private bool ParseArgument() 307 | { 308 | if (!ConsumeArgumentName()) 309 | return false; 310 | 311 | if (!ConsumePunctuator(TokenKind.Colon)) 312 | throw new DiagnosticException(ErrorMessages.ExpectedColon); 313 | 314 | if (!ParseValue()) 315 | throw new DiagnosticException(ErrorMessages.ExpectedValue); 316 | 317 | return true; 318 | } 319 | 320 | private bool ParseValue() 321 | { 322 | return 323 | ConsumeVariableName() || 324 | ConsumeNumber() || 325 | ConsumeString() || 326 | ConsumeLiteral() || 327 | ConsumeEnum() || 328 | ParseListValue() || 329 | ParseObjectValue(); 330 | } 331 | 332 | private bool ParseObjectValue() 333 | { 334 | if (!ConsumePunctuator(TokenKind.LeftBrace)) 335 | return false; 336 | 337 | while (ParseObjectField()); 338 | 339 | if (!ConsumePunctuator(TokenKind.RightBrace)) 340 | throw new DiagnosticException(ErrorMessages.ExpectedRightBrace); 341 | 342 | return true; 343 | } 344 | 345 | private bool ParseObjectField() 346 | { 347 | if (!ConsumeObjectFieldName()) 348 | return false; 349 | 350 | if (!ConsumePunctuator(TokenKind.Colon)) 351 | throw new DiagnosticException(ErrorMessages.ExpectedColon); 352 | 353 | if (!ParseValue()) 354 | throw new DiagnosticException(ErrorMessages.ExpectedValue); 355 | 356 | return true; 357 | } 358 | 359 | private bool ParseListValue() 360 | { 361 | if (!ConsumePunctuator(TokenKind.LeftBracket)) 362 | return false; 363 | 364 | while (ParseValue()); 365 | 366 | if (!ConsumePunctuator(TokenKind.RightBracket)) 367 | throw new DiagnosticException(ErrorMessages.ExpectedRightBracket); 368 | 369 | return true; 370 | } 371 | 372 | private bool ParseVariableDefinitions() 373 | { 374 | if (!ConsumePunctuator(TokenKind.LeftParenthesis)) 375 | return false; 376 | 377 | if (!ParseVariableDefinition()) 378 | throw new DiagnosticException(ErrorMessages.ExpectedVariableDefinition); 379 | 380 | while (ParseVariableDefinition()); 381 | 382 | if (!ConsumePunctuator(TokenKind.RightParenthesis)) 383 | throw new DiagnosticException(ErrorMessages.ExpectedRightParenthesis); 384 | 385 | return true; 386 | } 387 | 388 | private bool ParseVariableDefinition() 389 | { 390 | if (!ConsumeVariableName()) 391 | return false; 392 | 393 | if (!ConsumePunctuator(TokenKind.Colon)) 394 | throw new DiagnosticException(ErrorMessages.ExpectedColon); 395 | 396 | if (!ParseType()) 397 | throw new DiagnosticException(ErrorMessages.ExpectedType); 398 | 399 | ParseDefaultValue(); 400 | 401 | return true; 402 | } 403 | 404 | private bool ParseType() 405 | { 406 | return ParseNamedOrNotNullType() || ParseListOrNotNullType(); 407 | } 408 | 409 | private bool ParseNamedOrNotNullType() 410 | { 411 | if (!ConsumeNamedType()) 412 | return false; 413 | 414 | ConsumePunctuator(TokenKind.Bang); 415 | return true; 416 | } 417 | 418 | private bool ParseListOrNotNullType() 419 | { 420 | if (!ConsumePunctuator(TokenKind.LeftBracket)) 421 | return false; 422 | 423 | if (!ParseType()) 424 | throw new DiagnosticException(ErrorMessages.ExpectedType); 425 | 426 | if (!ConsumePunctuator(TokenKind.RightBracket)) 427 | throw new DiagnosticException(ErrorMessages.ExpectedRightBracket); 428 | 429 | ConsumePunctuator(TokenKind.Bang); 430 | 431 | return true; 432 | } 433 | 434 | private bool ParseDefaultValue() 435 | { 436 | if (!ConsumePunctuator(TokenKind.Equals)) 437 | return false; 438 | 439 | if (!ParseValue()) 440 | throw new DiagnosticException(ErrorMessages.ExpectedValue); 441 | 442 | return true; 443 | } 444 | 445 | private bool ParseFragmentDefinition() 446 | { 447 | if (!ConsumeFragmentKeyword()) 448 | return false; 449 | 450 | if (!ConsumeFragmentName()) 451 | throw new DiagnosticException(ErrorMessages.ExpectedFragmentName); 452 | 453 | if (!ParseTypeCondition()) 454 | throw new DiagnosticException(ErrorMessages.ExpectedTypeCondition); 455 | 456 | ParseDirectives(); 457 | 458 | if (!ParseSelectionSet()) 459 | throw new DiagnosticException(ErrorMessages.ExpectedSelectionSet); 460 | 461 | return true; 462 | } 463 | 464 | private bool ConsumeOperationType() 465 | { 466 | if (!Match(TokenKind.Name, s_operationTypes)) 467 | return false; 468 | 469 | _spans.AddKeyword(_lexer.Span); 470 | MoveNext(); 471 | 472 | return true; 473 | } 474 | 475 | private bool ConsumeOperationName() 476 | { 477 | if (!Match(TokenKind.Name)) 478 | return false; 479 | 480 | _spans.AddOperationName(_lexer.Span); 481 | MoveNext(); 482 | 483 | return true; 484 | } 485 | 486 | private bool ConsumeFragmentName() 487 | { 488 | if (!MatchButNot(TokenKind.Name, s_onKeyword)) 489 | return false; 490 | 491 | _spans.AddFragmentName(_lexer.Span); 492 | MoveNext(); 493 | 494 | return true; 495 | } 496 | 497 | private bool ConsumeNamedType() 498 | { 499 | if (!Match(TokenKind.Name)) 500 | return false; 501 | 502 | _spans.AddTypeName(_lexer.Span); 503 | MoveNext(); 504 | 505 | return true; 506 | } 507 | 508 | private bool ConsumeFieldAlias() 509 | { 510 | if (_lexer.Lookahead() != TokenKind.Colon) 511 | return false; 512 | 513 | if (!Match(TokenKind.Name)) 514 | return false; 515 | 516 | _spans.AddFieldName(_lexer.Span); 517 | MoveNext(); 518 | Debug.Assert(_lexer.Kind == TokenKind.Colon); 519 | 520 | _spans.AddPunctuator(_lexer.Span); 521 | MoveNext(); 522 | 523 | return true; 524 | } 525 | 526 | private bool ConsumeFieldName(bool hasAlias) 527 | { 528 | if (!Match(TokenKind.Name)) 529 | return false; 530 | 531 | if (hasAlias) 532 | { 533 | _spans.AddAliasedFieldName(_lexer.Span); 534 | } 535 | else 536 | { 537 | _spans.AddFieldName(_lexer.Span); 538 | } 539 | MoveNext(); 540 | 541 | return true; 542 | } 543 | 544 | private bool ConsumeObjectFieldName() 545 | { 546 | if (!Match(TokenKind.Name)) 547 | return false; 548 | 549 | _spans.AddObjectFieldName(_lexer.Span); 550 | MoveNext(); 551 | 552 | return true; 553 | } 554 | 555 | private bool ConsumeVariableName() 556 | { 557 | if (!Match(TokenKind.VariableName)) 558 | return false; 559 | 560 | _spans.AddVariableName(_lexer.Span); 561 | MoveNext(); 562 | 563 | return true; 564 | } 565 | 566 | private bool ConsumeDirectiveName() 567 | { 568 | if (!Match(TokenKind.DirectiveName)) 569 | return false; 570 | 571 | _spans.AddDirectiveName(_lexer.Span); 572 | MoveNext(); 573 | 574 | return true; 575 | } 576 | 577 | private bool ConsumeArgumentName() 578 | { 579 | if (!Match(TokenKind.Name)) 580 | return false; 581 | 582 | _spans.AddArgumentName(_lexer.Span); 583 | MoveNext(); 584 | 585 | return true; 586 | } 587 | 588 | private bool ConsumeString() 589 | { 590 | if (!Match(TokenKind.String) && !Match(TokenKind.BlockString)) 591 | return false; 592 | 593 | _spans.AddString(_lexer.Span); 594 | MoveNext(); 595 | 596 | return true; 597 | } 598 | 599 | private bool ConsumeNumber() 600 | { 601 | if (!Match(TokenKind.Integer) && !Match(TokenKind.Float)) 602 | return false; 603 | 604 | _spans.AddNumber(_lexer.Span); 605 | MoveNext(); 606 | 607 | return true; 608 | } 609 | 610 | private bool ConsumeOnKeyword() 611 | { 612 | if (!Match(TokenKind.Name, s_onKeyword)) 613 | return false; 614 | 615 | _spans.AddKeyword(_lexer.Span); 616 | MoveNext(); 617 | 618 | return true; 619 | } 620 | 621 | private bool ConsumeFragmentKeyword() 622 | { 623 | if (!Match(TokenKind.Name, s_fragmentKeyword)) 624 | return false; 625 | 626 | _spans.AddKeyword(_lexer.Span); 627 | MoveNext(); 628 | 629 | return true; 630 | } 631 | 632 | private bool ConsumeEnum() 633 | { 634 | if (!Match(TokenKind.Name)) 635 | return false; 636 | 637 | _spans.AddEnum(_lexer.Span); 638 | MoveNext(); 639 | 640 | return true; 641 | } 642 | 643 | private bool ConsumeLiteral() 644 | { 645 | if (!Match(TokenKind.Name, s_literals)) 646 | return false; 647 | 648 | _spans.AddKeyword(_lexer.Span); 649 | MoveNext(); 650 | 651 | return true; 652 | } 653 | 654 | private bool ConsumePunctuator(TokenKind kind) 655 | { 656 | if (!Match(kind)) 657 | return false; 658 | 659 | _spans.AddPunctuator(_lexer.Span); 660 | MoveNext(); 661 | 662 | return true; 663 | } 664 | 665 | private bool Match(TokenKind kind) 666 | { 667 | return _lexer.Kind == kind; 668 | } 669 | 670 | private readonly bool Match(TokenKind kind, string[] values) 671 | { 672 | if (_lexer.Kind != kind) 673 | return false; 674 | 675 | foreach (string value in values) 676 | { 677 | if (_lexer.ValueMatch(value)) 678 | return true; 679 | } 680 | 681 | return false; 682 | } 683 | 684 | private readonly bool MatchButNot(TokenKind kind, string[] values) 685 | { 686 | if (_lexer.Kind != kind) 687 | return false; 688 | 689 | foreach (string value in values) 690 | { 691 | if (_lexer.ValueMatch(value)) 692 | return false; 693 | } 694 | 695 | return true; 696 | } 697 | 698 | private void MoveNext() 699 | { 700 | while (_lexer.MoveNext()) 701 | { 702 | switch (_lexer.Kind) 703 | { 704 | case TokenKind.Comment: 705 | _spans.AddComment(_lexer.Span); 706 | break; 707 | 708 | case TokenKind.Comma: 709 | _spans.AddPunctuator(_lexer.Span); 710 | break; 711 | 712 | case TokenKind.Error: 713 | throw new DiagnosticException(_lexer.ErrorMessage ?? string.Empty); 714 | 715 | case TokenKind.BadSource: 716 | throw new BadSourceException(); 717 | 718 | default: 719 | return; 720 | } 721 | } 722 | } 723 | 724 | public static void Parse(SourceText source, ISyntaxSpanCollection spans) 725 | { 726 | new GqlParser(spans, new GqlLexer(source)).Parse(); 727 | } 728 | 729 | private sealed class DiagnosticException : Exception 730 | { 731 | public DiagnosticException(string message) 732 | : base(message) 733 | { 734 | } 735 | } 736 | 737 | private sealed class BadSourceException : Exception 738 | { 739 | } 740 | 741 | private sealed class AnonymousOperationException : Exception 742 | { 743 | public AnonymousOperationException() 744 | : base(ErrorMessages.AnonymousOperationError) 745 | { 746 | } 747 | } 748 | } 749 | -------------------------------------------------------------------------------- /GraphQLTools.Core/Syntax/ISyntaxSpanCollection.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis.Text; 2 | 3 | namespace GraphQLTools.Syntax; 4 | 5 | internal interface ISyntaxSpanCollection 6 | { 7 | void AddPunctuator(TextSpan span); 8 | void AddKeyword(TextSpan span); 9 | void AddOperationName(TextSpan span); 10 | void AddFragmentName(TextSpan span); 11 | void AddVariableName(TextSpan span); 12 | void AddDirectiveName(TextSpan span); 13 | void AddTypeName(TextSpan span); 14 | void AddFieldName(TextSpan span); 15 | void AddAliasedFieldName(TextSpan span); 16 | void AddArgumentName(TextSpan span); 17 | void AddObjectFieldName(TextSpan span); 18 | void AddString(TextSpan span); 19 | void AddNumber(TextSpan span); 20 | void AddEnum(TextSpan span); 21 | void AddName(TextSpan span); 22 | void AddComment(TextSpan span); 23 | void AddError(TextSpan span); 24 | 25 | void SetDiagnostic(TextSpan span, string message); 26 | } 27 | -------------------------------------------------------------------------------- /GraphQLTools.Core/Syntax/SourceText.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | 3 | namespace GraphQLTools.Syntax; 4 | 5 | internal abstract class SourceText 6 | { 7 | public const char InvalidChar = '\uFFFF'; 8 | 9 | protected SourceText(int position) 10 | { 11 | Position = position; 12 | } 13 | 14 | public int Position { get; private set; } 15 | 16 | public char Current { get; private set; } 17 | 18 | protected abstract int Start { get; } 19 | 20 | protected abstract int End { get; } 21 | 22 | protected abstract char MoveNext(ref int position); 23 | 24 | public LookaheadCheckpoint Checkpoint() => new LookaheadCheckpoint(this); 25 | 26 | public bool MoveNext() 27 | { 28 | int position = Position; 29 | if (position == End) 30 | return false; 31 | 32 | char next = MoveNext(ref position); 33 | 34 | Position = position; 35 | Current = next; 36 | return true; 37 | } 38 | 39 | public bool Match(string expected) 40 | { 41 | Debug.Assert(expected.Length > 0); 42 | 43 | int position = Position - expected.Length; 44 | if (position < Start) 45 | return false; 46 | 47 | char next = MoveNext(ref position); 48 | if (next != expected[0]) 49 | return false; 50 | 51 | for (int i = 1; i < expected.Length; i++) 52 | { 53 | if (position == End) 54 | return false; 55 | 56 | next = MoveNext(ref position); 57 | 58 | if (next != expected[i]) 59 | return false; 60 | } 61 | 62 | return true; 63 | } 64 | 65 | public bool Eat(char expected) 66 | { 67 | int position = Position; 68 | if (position == End) 69 | return false; 70 | 71 | char next = MoveNext(ref position); 72 | 73 | if (next != expected) 74 | return false; 75 | 76 | Position = position; 77 | Current = next; 78 | return true; 79 | } 80 | 81 | public bool Eat(string expected) 82 | { 83 | Debug.Assert(expected.Length > 0); 84 | 85 | int position = Position; 86 | if (position == End) 87 | return false; 88 | 89 | char next = MoveNext(ref position); 90 | if (next != expected[0]) 91 | return false; 92 | 93 | for (int i = 1; i < expected.Length; i++) 94 | { 95 | if (position == End) 96 | return false; 97 | 98 | next = MoveNext(ref position); 99 | 100 | if (next != expected[i]) 101 | return false; 102 | } 103 | 104 | Position = position; 105 | Current = next; 106 | return true; 107 | } 108 | 109 | public bool Eat(Predicate predicate) 110 | { 111 | int position = Position; 112 | if (position == End) 113 | return false; 114 | 115 | char next = MoveNext(ref position); 116 | 117 | if (!predicate(next)) 118 | return false; 119 | 120 | Position = position; 121 | Current = next; 122 | return true; 123 | } 124 | 125 | public bool EatWhile(Predicate predicate) 126 | { 127 | int position = Position; 128 | if (position == End) 129 | return false; 130 | 131 | char next = MoveNext(ref position); 132 | 133 | if (!predicate(next)) 134 | return false; 135 | 136 | do 137 | { 138 | Position = position; 139 | Current = next; 140 | 141 | if (position == End) 142 | return true; 143 | 144 | next = MoveNext(ref position); 145 | } 146 | while (predicate(next)); 147 | 148 | return true; 149 | } 150 | 151 | public bool EatUntil(Predicate predicate) 152 | { 153 | int position = Position; 154 | if (position == End) 155 | return false; 156 | 157 | char next = MoveNext(ref position); 158 | 159 | if (predicate(next)) 160 | return false; 161 | 162 | do 163 | { 164 | Position = position; 165 | Current = next; 166 | 167 | if (position == End) 168 | return true; 169 | 170 | next = MoveNext(ref position); 171 | } 172 | while (!predicate(next)); 173 | 174 | return true; 175 | } 176 | 177 | public readonly ref struct LookaheadCheckpoint 178 | { 179 | private readonly SourceText _source; 180 | private readonly int _position; 181 | 182 | public LookaheadCheckpoint(SourceText source) 183 | { 184 | _source = source; 185 | _position = source.Position; 186 | } 187 | 188 | public void Reset() 189 | { 190 | _source.Position = _position; 191 | } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /GraphQLTools.Core/Syntax/SyntaxKind.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQLTools.Syntax; 2 | 3 | internal enum SyntaxKind // Not really representing syntax, but whatever. 4 | { 5 | Punctuator, 6 | Keyword, 7 | OperationName, 8 | FragmentName, 9 | VariableName, 10 | DirectiveName, 11 | TypeName, 12 | FieldName, 13 | AliasedFieldName, 14 | ArgumentName, 15 | ObjectFieldName, 16 | String, 17 | Number, 18 | Enum, 19 | Name, 20 | Comment, 21 | Error 22 | } 23 | -------------------------------------------------------------------------------- /GraphQLTools.Core/Syntax/SyntaxSpan.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis.Text; 2 | 3 | namespace GraphQLTools.Syntax; 4 | 5 | internal readonly struct SyntaxSpan 6 | { 7 | private SyntaxSpan(SyntaxKind kind, int start, int length) 8 | { 9 | Kind = kind; 10 | Start = start; 11 | Length = length; 12 | } 13 | 14 | public SyntaxKind Kind { get; } 15 | 16 | public int Start { get; } 17 | 18 | public int Length { get; } 19 | 20 | public int End => Start + Length; 21 | 22 | public SyntaxSpan Shift(int offset) => new SyntaxSpan(Kind, Start + offset, Length); 23 | 24 | public SyntaxSpan WithSpan(int start, int length) => new SyntaxSpan(Kind, start, length); 25 | 26 | public static SyntaxSpan Create(SyntaxKind kind, TextSpan span) => new SyntaxSpan(kind, span.Start, span.Length); 27 | } 28 | -------------------------------------------------------------------------------- /GraphQLTools.Core/Syntax/TextFacts.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | namespace GraphQLTools.Syntax; 4 | 5 | internal static class TextFacts 6 | { 7 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 8 | public static bool IsAlpha(char c) => c is 9 | >= 'a' and <= 'z' or 10 | >= 'A' and <= 'Z' or 11 | '_'; 12 | 13 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 14 | public static bool IsDigit(char c) => c is >= '0' and <= '9'; 15 | 16 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 17 | public static bool IsAlphaNumeric(char c) => IsAlpha(c) || IsDigit(c); 18 | 19 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 20 | public static bool IsIgnored(char c) => c is ' ' or '\t' or ',' || IsNewlineCharacter(c); 21 | 22 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 23 | public static bool IsNewlineCharacter(char c) => c is '\n' or '\r'; 24 | 25 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 26 | public static bool IsExponentIndicator(char c) => c is 'e' or 'E'; 27 | 28 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 29 | public static bool IsSign(char c) => c is '+' or '-'; 30 | 31 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 32 | public static bool IsWordLike(char c) => IsAlphaNumeric(c) || IsSign(c) || c is '.'; 33 | 34 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 35 | public static bool IsEscaped(char c) => c is '"' or '\\' or '/' or 'b' or 'f' or 'n' or 'r' or 't'; 36 | 37 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 38 | public static bool IsHexDigit(char c) 39 | { 40 | return c is 41 | >= '0' and <= '9' or 42 | >= 'A' and <= 'F' or 43 | >= 'a' and <= 'f'; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /GraphQLTools.Core/Syntax/TokenKind.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQLTools.Syntax; 2 | 3 | internal enum TokenKind 4 | { 5 | EndOfFile, // 6 | StartOfFile, // 7 | Error, // N/D 8 | BadSource, // N/D 9 | Comma, // , 10 | Bang, // ! 11 | QuestionMark, // ? 12 | Ampersand, // & 13 | LeftParenthesis, // ( 14 | RightParenthesis, // ) 15 | LeftBracket, // [ 16 | RightBracket, // ] 17 | LeftBrace, // { 18 | RightBrace, // } 19 | Spread, // ... 20 | Colon, // : 21 | Equals, // = 22 | Pipe, // | 23 | Name, // name 24 | VariableName, // $name 25 | DirectiveName, // @name 26 | String, // "string" 27 | BlockString, // """line1 \n line2""" 28 | Integer, // 42 29 | Float, // 69.420 30 | Comment // # comment 31 | } 32 | -------------------------------------------------------------------------------- /GraphQLTools.Core/Utils/RoslynExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using Microsoft.CodeAnalysis.CSharp.Syntax; 3 | using Microsoft.CodeAnalysis.Operations; 4 | using System.Collections.Immutable; 5 | 6 | namespace GraphQLTools.Utils; 7 | 8 | internal static class RoslynExtensions 9 | { 10 | public static bool IsGqlString(this SemanticModel semanticModel, ArgumentSyntax argument, CancellationToken cancellationToken) 11 | { 12 | IOperation? operation = semanticModel.GetOperation(argument, cancellationToken); 13 | if (operation is not IArgumentOperation { Parameter: { Type.SpecialType: SpecialType.System_String } parameter }) 14 | return false; 15 | 16 | return parameter.IsGqlString(); 17 | } 18 | 19 | public static bool IsGqlString(this SemanticModel semanticModel, VariableDeclaratorSyntax variableDeclarator, CancellationToken cancellationToken) 20 | { 21 | ISymbol? declaredSymbol = semanticModel.GetDeclaredSymbol(variableDeclarator, cancellationToken); 22 | if (declaredSymbol is not IFieldSymbol) 23 | return false; 24 | 25 | return declaredSymbol.IsGqlString(); 26 | } 27 | 28 | public static bool IsGqlString(this SemanticModel semanticModel, PropertyDeclarationSyntax propertyDeclaration, CancellationToken cancellationToken) 29 | { 30 | ISymbol? declaredSymbol = semanticModel.GetDeclaredSymbol(propertyDeclaration, cancellationToken); 31 | if (declaredSymbol is not IPropertySymbol) 32 | return false; 33 | 34 | return declaredSymbol.IsGqlString(); 35 | } 36 | 37 | private static bool IsGqlString(this ISymbol symbol) 38 | { 39 | ImmutableArray attributes = symbol.GetAttributes(); 40 | foreach (AttributeData attribute in attributes) 41 | { 42 | INamedTypeSymbol? attributeClass = attribute.AttributeClass; 43 | if (attributeClass is null) 44 | continue; 45 | 46 | if (attributeClass.Name != "StringSyntaxAttribute") 47 | continue; 48 | 49 | INamespaceSymbol @namespace = attributeClass.ContainingNamespace; 50 | if (@namespace is null) 51 | continue; 52 | 53 | if (@namespace.Name != "CodeAnalysis") 54 | continue; 55 | 56 | @namespace = @namespace.ContainingNamespace; 57 | if (@namespace is null) 58 | continue; 59 | 60 | if (@namespace.Name != "Diagnostics") 61 | continue; 62 | 63 | @namespace = @namespace.ContainingNamespace; 64 | if (@namespace is null) 65 | continue; 66 | 67 | if (@namespace.Name != "System") 68 | continue; 69 | 70 | @namespace = @namespace.ContainingNamespace; 71 | if (@namespace is null) 72 | continue; 73 | 74 | if (!@namespace.IsGlobalNamespace) 75 | continue; 76 | 77 | ImmutableArray constructorArguments = attribute.ConstructorArguments; 78 | if (constructorArguments.Length == 0) 79 | continue; 80 | 81 | TypedConstant constructorArgument = constructorArguments[0]; 82 | if (constructorArgument.Type is not { SpecialType: SpecialType.System_String }) 83 | continue; 84 | 85 | if (constructorArgument.Value is string value && value.Equals("graphql", StringComparison.InvariantCultureIgnoreCase)) 86 | return true; 87 | } 88 | 89 | return false; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /GraphQLTools.Tests/GraphQLTools.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | Debug 7 | AnyCPU 8 | {581E005C-932B-44D1-97D4-BD1F4E9A7806} 9 | Library 10 | Properties 11 | GraphQLTools 12 | GraphQLTools.Tests 13 | v4.7.2 14 | 512 15 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 16 | 15.0 17 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 18 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages 19 | False 20 | UnitTest 21 | 22 | 23 | 24 | 25 | true 26 | full 27 | false 28 | bin\Debug\ 29 | DEBUG;TRACE 30 | prompt 31 | 4 32 | 33 | 34 | pdbonly 35 | true 36 | bin\Release\ 37 | TRACE 38 | prompt 39 | 4 40 | 41 | 42 | 43 | ..\packages\Castle.Core.5.1.1\lib\net462\Castle.Core.dll 44 | 45 | 46 | ..\packages\Microsoft.Bcl.AsyncInterfaces.7.0.0\lib\net462\Microsoft.Bcl.AsyncInterfaces.dll 47 | 48 | 49 | 50 | ..\packages\Microsoft.VisualStudio.CoreUtility.17.7.188\lib\net472\Microsoft.VisualStudio.CoreUtility.dll 51 | 52 | 53 | ..\packages\MSTest.TestFramework.3.1.1\lib\net462\Microsoft.VisualStudio.TestPlatform.TestFramework.dll 54 | 55 | 56 | ..\packages\MSTest.TestFramework.3.1.1\lib\net462\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll 57 | 58 | 59 | ..\packages\Microsoft.VisualStudio.Text.Data.17.7.188\lib\net472\Microsoft.VisualStudio.Text.Data.dll 60 | 61 | 62 | ..\packages\Microsoft.VisualStudio.Threading.17.7.30\lib\net472\Microsoft.VisualStudio.Threading.dll 63 | 64 | 65 | ..\packages\Microsoft.VisualStudio.Validation.17.6.11\lib\netstandard2.0\Microsoft.VisualStudio.Validation.dll 66 | 67 | 68 | ..\packages\Microsoft.Win32.Registry.5.0.0\lib\net461\Microsoft.Win32.Registry.dll 69 | 70 | 71 | ..\packages\NSubstitute.5.1.0\lib\net462\NSubstitute.dll 72 | 73 | 74 | 75 | ..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll 76 | 77 | 78 | ..\packages\System.Collections.Immutable.7.0.0\lib\net462\System.Collections.Immutable.dll 79 | 80 | 81 | 82 | 83 | 84 | ..\packages\System.Memory.4.5.5\lib\net461\System.Memory.dll 85 | 86 | 87 | 88 | ..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll 89 | 90 | 91 | ..\packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll 92 | 93 | 94 | ..\packages\System.Security.AccessControl.6.0.0\lib\net461\System.Security.AccessControl.dll 95 | 96 | 97 | ..\packages\System.Security.Principal.Windows.5.0.0\lib\net461\System.Security.Principal.Windows.dll 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | {b011be80-7b51-4f44-9c9d-ce06f4548572} 111 | GraphQLTools.Core 112 | 113 | 114 | {82156af4-fcbc-4902-b6c5-e29d402bf0e4} 115 | GraphQLTools 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | -------------------------------------------------------------------------------- /GraphQLTools.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | [assembly: AssemblyTitle("GraphQLTools.Tests")] 5 | [assembly: AssemblyDescription("")] 6 | [assembly: AssemblyConfiguration("")] 7 | [assembly: AssemblyCompany("")] 8 | [assembly: AssemblyProduct("GraphQLTools.Tests")] 9 | [assembly: AssemblyCopyright("Copyright © 2023")] 10 | [assembly: AssemblyTrademark("")] 11 | [assembly: AssemblyCulture("")] 12 | 13 | [assembly: ComVisible(false)] 14 | 15 | [assembly: Guid("581e005c-932b-44d1-97d4-bd1f4e9a7806")] 16 | 17 | [assembly: AssemblyVersion("1.0.0.0")] 18 | [assembly: AssemblyFileVersion("1.0.0.0")] 19 | -------------------------------------------------------------------------------- /GraphQLTools.Tests/Syntax/GqlParserTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis.Text; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using NSubstitute; 4 | 5 | namespace GraphQLTools.Syntax 6 | { 7 | [TestClass] 8 | public class GqlParserTests 9 | { 10 | [TestMethod] 11 | public void Parse_ShouldAddNoDiagnostics_WhenDocumentIsWellFormed() 12 | { 13 | // Arrange 14 | ISyntaxSpanCollection spans = Substitute.For(); 15 | var source = new StringSourceText("query MyQuery ($var: [String!]!) @dir(arg: 12) { field1 alias: field2(arg1: $var, arg2: ENUM) @dir(arg: \"str\") { subfield } }"); 16 | 17 | // Act 18 | GqlParser.Parse(source, spans); 19 | 20 | // Assert 21 | spans.Received().AddKeyword(new TextSpan(0, 5)); 22 | spans.Received().AddOperationName(new TextSpan(6, 7)); 23 | spans.Received().AddPunctuator(new TextSpan(14, 1)); 24 | spans.Received().AddVariableName(new TextSpan(15, 4)); 25 | spans.Received().AddPunctuator(new TextSpan(19, 1)); 26 | spans.Received().AddPunctuator(new TextSpan(21, 1)); 27 | spans.Received().AddTypeName(new TextSpan(22, 6)); 28 | spans.Received().AddPunctuator(new TextSpan(28, 1)); 29 | spans.Received().AddPunctuator(new TextSpan(29, 1)); 30 | spans.Received().AddPunctuator(new TextSpan(30, 1)); 31 | spans.Received().AddPunctuator(new TextSpan(31, 1)); 32 | spans.Received().AddDirectiveName(new TextSpan(33, 4)); 33 | spans.Received().AddPunctuator(new TextSpan(37, 1)); 34 | spans.Received().AddArgumentName(new TextSpan(38, 3)); 35 | spans.Received().AddPunctuator(new TextSpan(41, 1)); 36 | spans.Received().AddNumber(new TextSpan(43, 2)); 37 | spans.Received().AddPunctuator(new TextSpan(45, 1)); 38 | spans.Received().AddPunctuator(new TextSpan(47, 1)); 39 | spans.Received().AddFieldName(new TextSpan(49, 6)); 40 | spans.Received().AddFieldName(new TextSpan(56, 5)); 41 | spans.Received().AddPunctuator(new TextSpan(61, 1)); 42 | spans.Received().AddAliasedFieldName(new TextSpan(63, 6)); 43 | spans.Received().AddPunctuator(new TextSpan(69, 1)); 44 | spans.Received().AddArgumentName(new TextSpan(70, 4)); 45 | spans.Received().AddPunctuator(new TextSpan(74, 1)); 46 | spans.Received().AddVariableName(new TextSpan(76, 4)); 47 | spans.Received().AddPunctuator(new TextSpan(80, 1)); 48 | spans.Received().AddArgumentName(new TextSpan(82, 4)); 49 | spans.Received().AddPunctuator(new TextSpan(86, 1)); 50 | spans.Received().AddEnum(new TextSpan(88, 4)); 51 | spans.Received().AddPunctuator(new TextSpan(92, 1)); 52 | spans.Received().AddDirectiveName(new TextSpan(94, 4)); 53 | spans.Received().AddPunctuator(new TextSpan(98, 1)); 54 | spans.Received().AddArgumentName(new TextSpan(99, 3)); 55 | spans.Received().AddPunctuator(new TextSpan(102, 1)); 56 | spans.Received().AddString(new TextSpan(104, 5)); 57 | spans.Received().AddPunctuator(new TextSpan(109, 1)); 58 | spans.Received().AddPunctuator(new TextSpan(111, 1)); 59 | spans.Received().AddFieldName(new TextSpan(113, 8)); 60 | spans.Received().AddPunctuator(new TextSpan(122, 1)); 61 | spans.Received().AddPunctuator(new TextSpan(124, 1)); 62 | } 63 | 64 | [TestMethod] 65 | public void Parse_ShouldAddDiagnostics_WhenDocumentHasSyntaxErrors() 66 | { 67 | // Arrange 68 | ISyntaxSpanCollection spans = Substitute.For(); 69 | var source = new StringSourceText("query ($var: ) { field }"); 70 | 71 | // Act 72 | GqlParser.Parse(source, spans); 73 | 74 | // Assert 75 | spans.Received().AddKeyword(new TextSpan(0, 5)); 76 | spans.Received().AddPunctuator(new TextSpan(6, 1)); 77 | spans.Received().AddVariableName(new TextSpan(7, 4)); 78 | spans.Received().AddPunctuator(new TextSpan(11, 1)); 79 | spans.Received().AddError(new TextSpan(13, 1)); 80 | spans.Received().SetDiagnostic(new TextSpan(13, 1), ErrorMessages.ExpectedType); 81 | spans.Received().AddPunctuator(new TextSpan(15, 1)); 82 | spans.Received().AddName(new TextSpan(17, 5)); 83 | spans.Received().AddPunctuator(new TextSpan(23, 1)); 84 | } 85 | 86 | private class StringSourceText : SourceText 87 | { 88 | private readonly string _source; 89 | 90 | public StringSourceText(string source) 91 | : base(0) 92 | { 93 | _source = source; 94 | } 95 | 96 | protected override int Start => 0; 97 | 98 | protected override int End => _source.Length; 99 | 100 | protected override char MoveNext(ref int position) 101 | { 102 | return _source[position++]; 103 | } 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /GraphQLTools.Tests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /GraphQLTools.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.7.34024.191 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GraphQLTools", "GraphQLTools\GraphQLTools.csproj", "{82156AF4-FCBC-4902-B6C5-E29D402BF0E4}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GraphQLTools.Tests", "GraphQLTools.Tests\GraphQLTools.Tests.csproj", "{581E005C-932B-44D1-97D4-BD1F4E9A7806}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{C37B3AD8-F484-486A-A8A5-883FD00D9A5B}" 11 | ProjectSection(SolutionItems) = preProject 12 | .editorconfig = .editorconfig 13 | EndProjectSection 14 | EndProject 15 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GraphQLTools.Core", "GraphQLTools.Core\GraphQLTools.Core.csproj", "{B011BE80-7B51-4F44-9C9D-CE06F4548572}" 16 | EndProject 17 | Global 18 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 19 | Debug|Any CPU = Debug|Any CPU 20 | Debug|arm64 = Debug|arm64 21 | Debug|x86 = Debug|x86 22 | Release|Any CPU = Release|Any CPU 23 | Release|arm64 = Release|arm64 24 | Release|x86 = Release|x86 25 | EndGlobalSection 26 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 27 | {82156AF4-FCBC-4902-B6C5-E29D402BF0E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 28 | {82156AF4-FCBC-4902-B6C5-E29D402BF0E4}.Debug|Any CPU.Build.0 = Debug|Any CPU 29 | {82156AF4-FCBC-4902-B6C5-E29D402BF0E4}.Debug|arm64.ActiveCfg = Debug|arm64 30 | {82156AF4-FCBC-4902-B6C5-E29D402BF0E4}.Debug|arm64.Build.0 = Debug|arm64 31 | {82156AF4-FCBC-4902-B6C5-E29D402BF0E4}.Debug|x86.ActiveCfg = Debug|x86 32 | {82156AF4-FCBC-4902-B6C5-E29D402BF0E4}.Debug|x86.Build.0 = Debug|x86 33 | {82156AF4-FCBC-4902-B6C5-E29D402BF0E4}.Release|Any CPU.ActiveCfg = Release|Any CPU 34 | {82156AF4-FCBC-4902-B6C5-E29D402BF0E4}.Release|Any CPU.Build.0 = Release|Any CPU 35 | {82156AF4-FCBC-4902-B6C5-E29D402BF0E4}.Release|arm64.ActiveCfg = Release|arm64 36 | {82156AF4-FCBC-4902-B6C5-E29D402BF0E4}.Release|arm64.Build.0 = Release|arm64 37 | {82156AF4-FCBC-4902-B6C5-E29D402BF0E4}.Release|x86.ActiveCfg = Release|x86 38 | {82156AF4-FCBC-4902-B6C5-E29D402BF0E4}.Release|x86.Build.0 = Release|x86 39 | {581E005C-932B-44D1-97D4-BD1F4E9A7806}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 40 | {581E005C-932B-44D1-97D4-BD1F4E9A7806}.Debug|Any CPU.Build.0 = Debug|Any CPU 41 | {581E005C-932B-44D1-97D4-BD1F4E9A7806}.Debug|arm64.ActiveCfg = Debug|Any CPU 42 | {581E005C-932B-44D1-97D4-BD1F4E9A7806}.Debug|arm64.Build.0 = Debug|Any CPU 43 | {581E005C-932B-44D1-97D4-BD1F4E9A7806}.Debug|x86.ActiveCfg = Debug|Any CPU 44 | {581E005C-932B-44D1-97D4-BD1F4E9A7806}.Debug|x86.Build.0 = Debug|Any CPU 45 | {581E005C-932B-44D1-97D4-BD1F4E9A7806}.Release|Any CPU.ActiveCfg = Release|Any CPU 46 | {581E005C-932B-44D1-97D4-BD1F4E9A7806}.Release|Any CPU.Build.0 = Release|Any CPU 47 | {581E005C-932B-44D1-97D4-BD1F4E9A7806}.Release|arm64.ActiveCfg = Release|Any CPU 48 | {581E005C-932B-44D1-97D4-BD1F4E9A7806}.Release|arm64.Build.0 = Release|Any CPU 49 | {581E005C-932B-44D1-97D4-BD1F4E9A7806}.Release|x86.ActiveCfg = Release|Any CPU 50 | {581E005C-932B-44D1-97D4-BD1F4E9A7806}.Release|x86.Build.0 = Release|Any CPU 51 | {B011BE80-7B51-4F44-9C9D-CE06F4548572}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 52 | {B011BE80-7B51-4F44-9C9D-CE06F4548572}.Debug|Any CPU.Build.0 = Debug|Any CPU 53 | {B011BE80-7B51-4F44-9C9D-CE06F4548572}.Debug|arm64.ActiveCfg = Debug|Any CPU 54 | {B011BE80-7B51-4F44-9C9D-CE06F4548572}.Debug|arm64.Build.0 = Debug|Any CPU 55 | {B011BE80-7B51-4F44-9C9D-CE06F4548572}.Debug|x86.ActiveCfg = Debug|Any CPU 56 | {B011BE80-7B51-4F44-9C9D-CE06F4548572}.Debug|x86.Build.0 = Debug|Any CPU 57 | {B011BE80-7B51-4F44-9C9D-CE06F4548572}.Release|Any CPU.ActiveCfg = Release|Any CPU 58 | {B011BE80-7B51-4F44-9C9D-CE06F4548572}.Release|Any CPU.Build.0 = Release|Any CPU 59 | {B011BE80-7B51-4F44-9C9D-CE06F4548572}.Release|arm64.ActiveCfg = Release|Any CPU 60 | {B011BE80-7B51-4F44-9C9D-CE06F4548572}.Release|arm64.Build.0 = Release|Any CPU 61 | {B011BE80-7B51-4F44-9C9D-CE06F4548572}.Release|x86.ActiveCfg = Release|Any CPU 62 | {B011BE80-7B51-4F44-9C9D-CE06F4548572}.Release|x86.Build.0 = Release|Any CPU 63 | EndGlobalSection 64 | GlobalSection(SolutionProperties) = preSolution 65 | HideSolutionNode = FALSE 66 | EndGlobalSection 67 | GlobalSection(ExtensibilityGlobals) = postSolution 68 | SolutionGuid = {E156B48A-B4AB-4312-AF31-A73CF97C0701} 69 | EndGlobalSection 70 | EndGlobal 71 | -------------------------------------------------------------------------------- /GraphQLTools/Analysis/AnalyzedSpan.cs: -------------------------------------------------------------------------------- 1 | using GraphQLTools.Syntax; 2 | using Microsoft.CodeAnalysis.CSharp; 3 | using Microsoft.CodeAnalysis.CSharp.Syntax; 4 | using Microsoft.CodeAnalysis.Text; 5 | using Microsoft.VisualStudio.Text; 6 | using System.Diagnostics; 7 | using SourceText = GraphQLTools.Syntax.SourceText; 8 | using SyntaxKind = Microsoft.CodeAnalysis.CSharp.SyntaxKind; 9 | 10 | namespace GraphQLTools.Analysis 11 | { 12 | internal abstract class AnalyzedSpan 13 | { 14 | private volatile SyntaxSpanList _spans; 15 | 16 | protected AnalyzedSpan(SyntaxKind syntaxKind, bool isUnterminated) 17 | { 18 | SyntaxKind = syntaxKind; 19 | IsUnterminated = isUnterminated; 20 | ParentSpanStart = -1; 21 | ExpressionSpanStart = -1; 22 | } 23 | 24 | public SyntaxKind SyntaxKind { get; } 25 | 26 | public bool IsUnterminated { get; } 27 | 28 | public int Start => ExpressionSpanStart; 29 | 30 | public int Length => ExpressionSpanLength; 31 | 32 | public int End => ExpressionSpanEnd; 33 | 34 | protected int ParentSpanStart { get; private set; } 35 | 36 | protected int ParentSpanLength { get; private set; } 37 | 38 | protected int ParentSpanEnd => ParentSpanStart + ParentSpanLength; 39 | 40 | protected int ExpressionSpanStart { get; private set; } 41 | 42 | protected int ExpressionSpanLength { get; private set; } 43 | 44 | protected int ExpressionSpanEnd => ExpressionSpanStart + ExpressionSpanLength; 45 | 46 | protected abstract int GqlSpanStart { get; } 47 | 48 | protected abstract int GqlSpanEnd { get; } 49 | 50 | protected abstract SourceText CreateSource(ITextSnapshot snapshot); 51 | 52 | public void Reparse(ITextSnapshot snapshot, CSharpSyntaxNode parent, LiteralExpressionSyntax expression, ref SyntaxSpanList spans) 53 | { 54 | TextSpan parentSpan = parent.Span; 55 | TextSpan expressionSpan = expression.Span; 56 | 57 | ParentSpanStart = parentSpan.Start; 58 | ParentSpanLength = parentSpan.Length; 59 | ExpressionSpanStart = expressionSpan.Start; 60 | ExpressionSpanLength = expressionSpan.Length; 61 | 62 | SourceText source = CreateSource(snapshot); 63 | 64 | try 65 | { 66 | lock (spans) 67 | { 68 | GqlParser.Parse(source, spans); 69 | } 70 | } 71 | finally 72 | { 73 | (_spans, spans) = (spans, _spans); 74 | } 75 | } 76 | 77 | public SyntaxSpanList ReturnSpanList() 78 | { 79 | Debug.Assert(_spans != null); 80 | 81 | SyntaxSpanList result = _spans; 82 | _spans = null; 83 | return result; 84 | } 85 | 86 | public void Synchronize(INormalizedTextChangeCollection changes) 87 | { 88 | int parentStart = ParentSpanStart; 89 | int parentEnd = ParentSpanEnd; 90 | int gqlStart = GqlSpanStart; 91 | int gqlEnd = GqlSpanEnd; 92 | 93 | for (int i = changes.Count - 1; i >= 0; i--) 94 | { 95 | ITextChange change = changes[i]; 96 | int changeStart = change.OldPosition; 97 | int changeEnd = change.OldEnd; 98 | int offset = change.Delta; 99 | 100 | if (parentEnd <= changeStart) // If the change is after us, take no action. 101 | continue; 102 | 103 | if (changeEnd <= parentStart) // If the change is completely before us, shift the spans' and spans' start positions. 104 | { 105 | ParentSpanStart += offset; 106 | ExpressionSpanStart += offset; 107 | _spans.Shift(offset); 108 | continue; 109 | } 110 | 111 | if (gqlStart <= changeStart && changeEnd <= gqlEnd) // If the change is comprised within the GQL span, adjust the spans' lengths and signal a reparse is needed. 112 | { 113 | ParentSpanLength += offset; 114 | ExpressionSpanLength += offset; 115 | _spans.Synchronize(change); 116 | continue; 117 | } 118 | 119 | // Otherwise, the analyzed span must be invalidated. 120 | ParentSpanStart = -1; 121 | ExpressionSpanStart = -1; 122 | ParentSpanLength = 0; 123 | ExpressionSpanLength = 0; 124 | return; 125 | } 126 | } 127 | 128 | public SyntaxSpanListSlice GetSyntaxSpansIn(Span span) 129 | { 130 | SyntaxSpanList spans = _spans; 131 | if (spans == null) 132 | return SyntaxSpanList.EmptySlice; 133 | 134 | int gqlStart = GqlSpanStart; 135 | int gqlEnd = GqlSpanEnd; 136 | if (span.End <= gqlStart || gqlEnd <= span.Start) 137 | return SyntaxSpanList.EmptySlice; 138 | 139 | return new SyntaxSpanListSlice(spans, span); 140 | } 141 | 142 | public bool TryGetDiagnosticSpanIn(Span span, out DiagnosticSpan diagnosticSpan) 143 | { 144 | DiagnosticSpan? maybeDiagnosticSpan = _spans?.DiagnosticSpan; 145 | if (maybeDiagnosticSpan == null) 146 | { 147 | diagnosticSpan = default; 148 | return false; 149 | } 150 | 151 | diagnosticSpan = maybeDiagnosticSpan.Value; 152 | 153 | if (span.IsEmpty) 154 | { 155 | int position = span.Start; 156 | return diagnosticSpan.Start <= position && position <= diagnosticSpan.End; 157 | } 158 | else 159 | { 160 | return diagnosticSpan.Start < span.End && span.Start < diagnosticSpan.End; 161 | } 162 | } 163 | 164 | public static bool TryCreate(ITextSnapshot snapshot, LiteralExpressionSyntax expression, out AnalyzedSpan result) 165 | { 166 | result = null; 167 | 168 | switch (expression.Token.Kind()) 169 | { 170 | case SyntaxKind.StringLiteralToken: 171 | switch (snapshot[expression.SpanStart]) 172 | { 173 | case '"': 174 | result = SimpleStringLiteralAnalyzedSpan.Create(snapshot, expression); 175 | return true; 176 | 177 | case '@': 178 | result = VerbatimStringLiteralAnalyzedSpan.Create(snapshot, expression); 179 | return true; 180 | 181 | default: 182 | return false; 183 | } 184 | 185 | case SyntaxKind.SingleLineRawStringLiteralToken: 186 | case SyntaxKind.MultiLineRawStringLiteralToken: 187 | result = RawStringLiteralAnalyzedSpan.Create(snapshot, expression); 188 | return true; 189 | 190 | default: 191 | return false; 192 | } 193 | } 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /GraphQLTools/Analysis/AnalyzedSpanBag.cs: -------------------------------------------------------------------------------- 1 | using GraphQLTools.Utils; 2 | using Microsoft.CodeAnalysis; 3 | using Microsoft.CodeAnalysis.CSharp; 4 | using Microsoft.CodeAnalysis.CSharp.Syntax; 5 | using Microsoft.CodeAnalysis.Text; 6 | using Microsoft.Extensions.ObjectPool; 7 | using Microsoft.VisualStudio.Text; 8 | using System; 9 | using System.Collections.Generic; 10 | using System.Linq; 11 | using System.Threading; 12 | 13 | namespace GraphQLTools.Analysis 14 | { 15 | internal readonly struct AnalyzedSpanBag 16 | { 17 | private static readonly LinkedList s_emptySpans = new LinkedList(); 18 | 19 | private readonly LinkedList _analyzedSpans; 20 | private readonly ObjectPool _spanListPool; 21 | private readonly object _synchronizationLock; 22 | 23 | public AnalyzedSpanBag(LinkedList analyzedSpans, ObjectPool spanListPool) 24 | { 25 | _analyzedSpans = analyzedSpans; 26 | _spanListPool = spanListPool; 27 | _synchronizationLock = new object(); 28 | } 29 | 30 | public void Clear() 31 | { 32 | lock (_synchronizationLock) 33 | { 34 | foreach (AnalyzedSpan analyzedSpan in _analyzedSpans) 35 | { 36 | _spanListPool.Return(analyzedSpan.ReturnSpanList()); 37 | } 38 | 39 | _analyzedSpans.Clear(); 40 | } 41 | } 42 | 43 | public Enumerator GetEnumerator() 44 | { 45 | if (!Monitor.TryEnter(_synchronizationLock, 100)) 46 | return new Enumerator(s_emptySpans, null, false); 47 | 48 | return new Enumerator(_analyzedSpans, _synchronizationLock, true); 49 | } 50 | 51 | public void Synchronize(INormalizedTextChangeCollection changes) 52 | { 53 | if (_analyzedSpans.Count == 0) 54 | return; 55 | 56 | lock (_synchronizationLock) 57 | { 58 | foreach (AnalyzedSpan analyzedSpan in _analyzedSpans) 59 | { 60 | analyzedSpan.Synchronize(changes); 61 | } 62 | } 63 | } 64 | 65 | public void RescanDocument(ITextSnapshot snapshot, SyntaxNode rootNode, SemanticModel semanticModel, CancellationToken cancellationToken) 66 | { 67 | Clear(); 68 | Rescan(snapshot, new Span(0, snapshot.Length), rootNode, semanticModel, cancellationToken); 69 | } 70 | 71 | public void Rescan(ITextSnapshot snapshot, INormalizedTextChangeCollection changes, SyntaxNode rootNode, SemanticModel semanticModel, CancellationToken cancellationToken) 72 | { 73 | if (changes.Count == 1) 74 | { 75 | Rescan(snapshot, changes[0].NewSpan, rootNode, semanticModel, cancellationToken); 76 | return; 77 | } 78 | 79 | Rescan(snapshot, new Span(0, snapshot.Length), rootNode, semanticModel, cancellationToken); 80 | } 81 | 82 | private void Rescan(ITextSnapshot snapshot, Span span, SyntaxNode rootNode, SemanticModel semanticModel, CancellationToken cancellationToken) 83 | { 84 | RemoveInvalidatedNodes(); 85 | 86 | IEnumerable nodes = GetNodesToAnalyze(rootNode, new TextSpan(span.Start, span.Length)); 87 | 88 | foreach (SyntaxNode node in nodes) 89 | { 90 | if (cancellationToken.IsCancellationRequested) 91 | return; 92 | 93 | switch (node) 94 | { 95 | case InvocationExpressionSyntax invocationExpression: 96 | foreach (ArgumentSyntax argument in invocationExpression.ArgumentList.Arguments) 97 | { 98 | if (!semanticModel.IsGqlString(argument, cancellationToken)) 99 | continue; 100 | 101 | ScanGqlExpression(snapshot, invocationExpression, argument.Expression); 102 | } 103 | break; 104 | 105 | case ObjectCreationExpressionSyntax objectCreationExpression: 106 | if (objectCreationExpression.ArgumentList is null) 107 | break; 108 | 109 | foreach (ArgumentSyntax argument in objectCreationExpression.ArgumentList.Arguments) 110 | { 111 | if (!semanticModel.IsGqlString(argument, cancellationToken)) 112 | continue; 113 | 114 | ScanGqlExpression(snapshot, objectCreationExpression, argument.Expression); 115 | } 116 | break; 117 | 118 | case FieldDeclarationSyntax fieldDeclaration: 119 | if (fieldDeclaration.Declaration is null) 120 | break; 121 | 122 | foreach (VariableDeclaratorSyntax variableDeclarator in fieldDeclaration.Declaration.Variables) 123 | { 124 | ExpressionSyntax fieldDeclaratorExpression = variableDeclarator.Initializer?.Value; 125 | if (fieldDeclaratorExpression is null) 126 | continue; 127 | 128 | if (!semanticModel.IsGqlString(variableDeclarator, cancellationToken)) 129 | continue; 130 | 131 | ScanGqlExpression(snapshot, fieldDeclaration, fieldDeclaratorExpression); 132 | } 133 | break; 134 | 135 | case PropertyDeclarationSyntax propertyDeclaration: 136 | ExpressionSyntax propertyExpression = propertyDeclaration.ExpressionBody?.Expression ?? propertyDeclaration.Initializer?.Value; 137 | if (propertyExpression is null) 138 | break; 139 | 140 | if (!semanticModel.IsGqlString(propertyDeclaration, cancellationToken)) 141 | break; 142 | 143 | ScanGqlExpression(snapshot, propertyDeclaration, propertyExpression); 144 | break; 145 | } 146 | } 147 | } 148 | 149 | private void ScanGqlExpression(ITextSnapshot snapshot, CSharpSyntaxNode parent, ExpressionSyntax expression) 150 | { 151 | int expressionSpanStart = expression.SpanStart; 152 | LiteralExpressionSyntax literalExpression = expression as LiteralExpressionSyntax; 153 | 154 | if (TryGetAt(expressionSpanStart, out LinkedListNode existingNode)) 155 | { 156 | ScanExistingGqlExpression(snapshot, parent, literalExpression, existingNode); 157 | return; 158 | } 159 | 160 | if (literalExpression == null || !AnalyzedSpan.TryCreate(snapshot, literalExpression, out AnalyzedSpan analyzedSpan)) 161 | return; 162 | 163 | SyntaxSpanList spans = _spanListPool.Get(); 164 | analyzedSpan.Reparse(snapshot, parent, literalExpression, ref spans); 165 | 166 | lock (_synchronizationLock) 167 | { 168 | _analyzedSpans.AddLast(analyzedSpan); 169 | } 170 | } 171 | 172 | private void ScanExistingGqlExpression(ITextSnapshot snapshot, CSharpSyntaxNode parent, LiteralExpressionSyntax literalExpression, LinkedListNode existingNode) 173 | { 174 | SyntaxSpanList spans; 175 | AnalyzedSpan existingAnalyzedSpan = existingNode.Value; 176 | 177 | if (!existingAnalyzedSpan.IsUnterminated && literalExpression != null && literalExpression.Token.IsKind(existingAnalyzedSpan.SyntaxKind)) 178 | { 179 | spans = _spanListPool.Get(); 180 | existingAnalyzedSpan.Reparse(snapshot, parent, literalExpression, ref spans); 181 | _spanListPool.Return(spans); 182 | return; 183 | } 184 | 185 | if (literalExpression == null || !AnalyzedSpan.TryCreate(snapshot, literalExpression, out AnalyzedSpan analyzedSpan)) 186 | { 187 | lock (_synchronizationLock) 188 | { 189 | RemoveNode(existingNode); 190 | } 191 | 192 | return; 193 | } 194 | 195 | spans = _spanListPool.Get(); 196 | analyzedSpan.Reparse(snapshot, parent, literalExpression, ref spans); 197 | 198 | lock (_synchronizationLock) 199 | { 200 | RemoveNode(existingNode); 201 | _analyzedSpans.AddLast(analyzedSpan); 202 | } 203 | } 204 | 205 | private void RemoveNode(LinkedListNode existingNode) 206 | { 207 | _spanListPool.Return(existingNode.Value.ReturnSpanList()); 208 | _analyzedSpans.Remove(existingNode); 209 | } 210 | 211 | private void RemoveInvalidatedNodes() 212 | { 213 | if (_analyzedSpans.Count == 0) 214 | return; 215 | 216 | lock (_synchronizationLock) 217 | { 218 | LinkedListNode node = _analyzedSpans.First; 219 | LinkedListNode last = node.Previous; 220 | 221 | do 222 | { 223 | LinkedListNode current = node; 224 | node = node.Next; 225 | 226 | if (current.Value.Length != 0) 227 | continue; 228 | 229 | RemoveNode(current); 230 | } 231 | while (node != last); 232 | } 233 | } 234 | 235 | private bool TryGetAt(int expressionSpanStart, out LinkedListNode node) 236 | { 237 | node = _analyzedSpans.First; 238 | if (node == null) 239 | return false; 240 | 241 | LinkedListNode last = node.Previous; 242 | 243 | do 244 | { 245 | if (node.Value.Start == expressionSpanStart) 246 | return true; 247 | 248 | node = node.Next; 249 | } 250 | while (node != last); 251 | 252 | node = null; 253 | return false; 254 | } 255 | 256 | private static IEnumerable GetNodesToAnalyze(SyntaxNode root, TextSpan span) 257 | { 258 | if (!span.IsEmpty) 259 | return root.DescendantNodes(span); 260 | 261 | int position = span.Start; 262 | if (position >= root.FullSpan.End) 263 | return Enumerable.Empty(); 264 | 265 | SyntaxNodeOrToken childAtPosition = root.ChildThatContainsPosition(span.Start); 266 | 267 | if (childAtPosition.IsNode) 268 | return childAtPosition.AsNode().DescendantNodes(); 269 | 270 | return Enumerable.Empty(); 271 | } 272 | 273 | public static AnalyzedSpanBag Create(ObjectPool spanListPool) 274 | { 275 | return new AnalyzedSpanBag(new LinkedList(), spanListPool); 276 | } 277 | 278 | public struct Enumerator : IDisposable 279 | { 280 | private LinkedList.Enumerator _enumerator; 281 | private readonly object _synchronizationLock; 282 | private readonly bool _lockTaken; 283 | 284 | public Enumerator(LinkedList analyzedSpans, object synchronizationLock, bool lockTaken) 285 | { 286 | _enumerator = analyzedSpans.GetEnumerator(); 287 | _synchronizationLock = synchronizationLock; 288 | _lockTaken = lockTaken; 289 | } 290 | 291 | public AnalyzedSpan Current => _enumerator.Current; 292 | 293 | public bool MoveNext() => _enumerator.MoveNext(); 294 | 295 | public void Dispose() 296 | { 297 | if (_lockTaken) 298 | { 299 | Monitor.Exit(_synchronizationLock); 300 | } 301 | } 302 | } 303 | } 304 | } 305 | -------------------------------------------------------------------------------- /GraphQLTools/Analysis/RawStringLiteralAnalyzedSpan.cs: -------------------------------------------------------------------------------- 1 | using GraphQLTools.Syntax; 2 | using Microsoft.CodeAnalysis.CSharp; 3 | using Microsoft.CodeAnalysis.CSharp.Syntax; 4 | using Microsoft.VisualStudio.Text; 5 | using SyntaxKind = Microsoft.CodeAnalysis.CSharp.SyntaxKind; 6 | 7 | namespace GraphQLTools.Analysis 8 | { 9 | internal sealed class RawStringLiteralAnalyzedSpan : AnalyzedSpan 10 | { 11 | private readonly int _quoteCount; 12 | 13 | public RawStringLiteralAnalyzedSpan(SyntaxKind syntaxKind, bool isUnterminated, int quoteCount) 14 | : base(syntaxKind, isUnterminated) 15 | { 16 | _quoteCount = quoteCount; 17 | } 18 | 19 | protected override int GqlSpanStart => ExpressionSpanStart + _quoteCount; 20 | 21 | protected override int GqlSpanEnd => IsUnterminated 22 | ? ExpressionSpanEnd 23 | : ExpressionSpanEnd - _quoteCount; 24 | 25 | protected override SourceText CreateSource(ITextSnapshot snapshot) 26 | { 27 | return new RawStringLiteralSourceText(snapshot, GqlSpanStart, GqlSpanEnd); 28 | } 29 | 30 | public static RawStringLiteralAnalyzedSpan Create(ITextSnapshot snapshot, LiteralExpressionSyntax expression) 31 | { 32 | int index = expression.SpanStart; 33 | int openingQuoteCount = 0; 34 | while (snapshot[index] == '"') 35 | { 36 | openingQuoteCount++; 37 | index++; 38 | } 39 | 40 | index = expression.Span.End - 1; 41 | int closingQuoteCount = 0; 42 | while (snapshot[index] == '"') 43 | { 44 | closingQuoteCount++; 45 | index--; 46 | } 47 | 48 | return new RawStringLiteralAnalyzedSpan(expression.Token.Kind(), openingQuoteCount != closingQuoteCount, openingQuoteCount); 49 | } 50 | 51 | private sealed class RawStringLiteralSourceText : SnapshotSourceText 52 | { 53 | public RawStringLiteralSourceText(ITextSnapshot snapshot, int start, int end) 54 | : base(snapshot, start, end) 55 | { 56 | } 57 | 58 | protected override char MoveNextCore(ref int position) 59 | { 60 | char current = Snapshot[position]; 61 | position++; 62 | 63 | return current; 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /GraphQLTools/Analysis/SimpleStringLiteralAnalyzedSpan.cs: -------------------------------------------------------------------------------- 1 | using GraphQLTools.Syntax; 2 | using Microsoft.CodeAnalysis.CSharp; 3 | using Microsoft.CodeAnalysis.CSharp.Syntax; 4 | using Microsoft.VisualStudio.Text; 5 | using System.Diagnostics; 6 | using static GraphQLTools.Syntax.TextFacts; 7 | using SyntaxKind = Microsoft.CodeAnalysis.CSharp.SyntaxKind; 8 | 9 | namespace GraphQLTools.Analysis 10 | { 11 | internal sealed class SimpleStringLiteralAnalyzedSpan : AnalyzedSpan 12 | { 13 | public SimpleStringLiteralAnalyzedSpan(SyntaxKind syntaxKind, bool isUnterminated) 14 | : base(syntaxKind, isUnterminated) 15 | { 16 | } 17 | 18 | protected override int GqlSpanStart => ExpressionSpanStart + 1; 19 | 20 | protected override int GqlSpanEnd => IsUnterminated 21 | ? ExpressionSpanEnd 22 | : ExpressionSpanEnd - 1; 23 | 24 | protected override SourceText CreateSource(ITextSnapshot snapshot) 25 | { 26 | return new SimpleStringLiteralSourceText(snapshot, GqlSpanStart, GqlSpanEnd); 27 | } 28 | 29 | public static SimpleStringLiteralAnalyzedSpan Create(ITextSnapshot snapshot, LiteralExpressionSyntax expression) 30 | { 31 | bool isUnterminated = !IsClosingSequence(snapshot, expression.Span.End - 1); 32 | return new SimpleStringLiteralAnalyzedSpan(expression.Token.Kind(), isUnterminated); 33 | } 34 | 35 | private static bool IsClosingSequence(ITextSnapshot snapshot, int position) 36 | { 37 | if (snapshot[position] != '"') 38 | return false; 39 | 40 | position--; 41 | int backslashCount = 0; 42 | while (snapshot[position] == '\\') 43 | { 44 | backslashCount++; 45 | position--; 46 | } 47 | 48 | return backslashCount % 2 == 0; 49 | } 50 | 51 | private sealed class SimpleStringLiteralSourceText : SnapshotSourceText // Based on Roslyn's lexer 52 | { 53 | private char _surrogateChar; 54 | 55 | public SimpleStringLiteralSourceText(ITextSnapshot snapshot, int start, int end) 56 | : base(snapshot, start, end) 57 | { 58 | } 59 | 60 | protected override char MoveNextCore(ref int position) 61 | { 62 | char current; 63 | 64 | if (_surrogateChar != default) 65 | { 66 | current = _surrogateChar; 67 | _surrogateChar = default; 68 | position++; 69 | return current; 70 | } 71 | 72 | current = Snapshot[position]; 73 | position++; 74 | 75 | if (current == '"') 76 | { 77 | Debug.Fail("Found unescaped double quotes."); 78 | return InvalidChar; 79 | } 80 | 81 | if (current != '\\') 82 | return current; 83 | 84 | if (position == End) 85 | return InvalidChar; 86 | 87 | current = Snapshot[position]; 88 | position++; 89 | 90 | switch (current) 91 | { 92 | // Escaped characters that translate to themselves. 93 | case '\'': 94 | case '"': 95 | case '\\': 96 | return current; 97 | 98 | // Translate escapes as per C# spec 2.4.4.4. 99 | case '0': 100 | return '\u0000'; 101 | 102 | case 'a': 103 | return '\u0007'; 104 | 105 | case 'b': 106 | return '\u0008'; 107 | 108 | case 'f': 109 | return '\u000c'; 110 | 111 | case 'n': 112 | return '\u000a'; 113 | 114 | case 'r': 115 | return '\u000d'; 116 | 117 | case 't': 118 | return '\u0009'; 119 | 120 | case 'v': 121 | return '\u000b'; 122 | 123 | case 'x': 124 | return GetUtf16EscapedChar(ref position, variableLength: true); 125 | 126 | case 'u': 127 | return GetUtf16EscapedChar(ref position, variableLength: false); 128 | 129 | case 'U': 130 | return GetUtf32EscapedChar(ref position, out _surrogateChar); 131 | 132 | default: 133 | return InvalidChar; 134 | } 135 | } 136 | 137 | private char GetUtf16EscapedChar(ref int position, bool variableLength) 138 | { 139 | if (position == End) 140 | return InvalidChar; 141 | 142 | char current = Snapshot[position]; 143 | position++; 144 | 145 | if (!IsHexDigit(current)) 146 | return InvalidChar; 147 | 148 | int codepoint = HexValue(current); 149 | 150 | for (int i = 0; i < 3; i++) 151 | { 152 | if (position == End) 153 | return InvalidChar; 154 | 155 | current = Snapshot[position]; 156 | position++; 157 | 158 | if (!IsHexDigit(current)) 159 | { 160 | if (!variableLength) 161 | return InvalidChar; 162 | 163 | break; 164 | } 165 | 166 | codepoint = (codepoint << 4) + HexValue(current); 167 | } 168 | 169 | return (char)codepoint; 170 | } 171 | 172 | private char GetUtf32EscapedChar(ref int position, out char surrogateChar) 173 | { 174 | surrogateChar = default; 175 | 176 | if (position == End) 177 | return InvalidChar; 178 | 179 | char current = Snapshot[position]; 180 | position++; 181 | 182 | if (!IsHexDigit(current)) 183 | return InvalidChar; 184 | 185 | int codepoint = HexValue(current); 186 | 187 | for (int i = 0; i < 7; i++) 188 | { 189 | if (position == End) 190 | return InvalidChar; 191 | 192 | current = Snapshot[position]; 193 | position++; 194 | 195 | if (!IsHexDigit(current)) 196 | return InvalidChar; 197 | 198 | codepoint = (codepoint << 4) + HexValue(current); 199 | } 200 | 201 | if (codepoint > 0x0010FFFF) 202 | return InvalidChar; 203 | 204 | if (codepoint >= 0x00010000) 205 | { 206 | surrogateChar = (char)((codepoint - 0x00010000) % 0x0400 + 0xDC00); 207 | return (char)((codepoint - 0x00010000) / 0x0400 + 0xD800); 208 | } 209 | 210 | return (char)codepoint; 211 | } 212 | } 213 | 214 | private static int HexValue(char c) 215 | { 216 | Debug.Assert(IsHexDigit(c)); 217 | 218 | return (c >= '0' && c <= '9') ? c - '0' : (c & 0xDF) - 'A' + 10; 219 | } 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /GraphQLTools/Analysis/SnapshotSourceText.cs: -------------------------------------------------------------------------------- 1 | using GraphQLTools.Syntax; 2 | using Microsoft.VisualStudio.Text; 3 | using System.Diagnostics; 4 | 5 | namespace GraphQLTools.Analysis 6 | { 7 | internal abstract class SnapshotSourceText : SourceText 8 | { 9 | protected SnapshotSourceText(ITextSnapshot snapshot, int start, int end) 10 | : base(start) 11 | { 12 | Snapshot = snapshot; 13 | Start = start; 14 | End = end; 15 | } 16 | 17 | protected ITextSnapshot Snapshot { get; } 18 | 19 | protected override int Start { get; } 20 | 21 | protected override int End { get; } 22 | 23 | protected abstract char MoveNextCore(ref int position); 24 | 25 | protected sealed override char MoveNext(ref int position) 26 | { 27 | Debug.Assert(position >= Start && position <= End); 28 | 29 | if (position == End) 30 | return '\0'; 31 | 32 | return MoveNextCore(ref position); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /GraphQLTools/Analysis/SyntaxSpanList.cs: -------------------------------------------------------------------------------- 1 | using GraphQLTools.Syntax; 2 | using Microsoft.CodeAnalysis.Text; 3 | using Microsoft.VisualStudio.Text; 4 | using System.Collections.Generic; 5 | using System.Diagnostics; 6 | 7 | namespace GraphQLTools.Analysis 8 | { 9 | internal class SyntaxSpanList : ISyntaxSpanCollection 10 | { 11 | public static readonly SyntaxSpanListSlice EmptySlice = new SyntaxSpanListSlice(new SyntaxSpanList(), default); 12 | 13 | private readonly List _list; 14 | 15 | private SyntaxSpanList() 16 | { 17 | _list = new List(); 18 | } 19 | 20 | public SyntaxSpanList(int capacity) 21 | { 22 | _list = new List(capacity); 23 | } 24 | 25 | public int Count => _list.Count; 26 | 27 | public DiagnosticSpan? DiagnosticSpan { get; private set; } 28 | 29 | public SyntaxSpan this[int index] => _list[index]; 30 | 31 | public void Shift(int offset) 32 | { 33 | if (DiagnosticSpan is DiagnosticSpan diagnosticSpan) 34 | { 35 | DiagnosticSpan = diagnosticSpan.Shift(offset); 36 | } 37 | 38 | for (int i = 0; i < _list.Count; i++) 39 | { 40 | _list[i] = _list[i].Shift(offset); 41 | } 42 | } 43 | 44 | public void Synchronize(ITextChange change) 45 | { 46 | int changeStart = change.OldPosition; 47 | int changeEnd = change.OldEnd; 48 | int offset = change.Delta; 49 | 50 | for (int i = 0; i < _list.Count; i++) 51 | { 52 | SyntaxSpan token = _list[i]; 53 | int tokenStart = token.Start; 54 | int tokenEnd = token.End; 55 | 56 | if (tokenEnd < changeStart) // If the change is completely after the token, take no action. 57 | continue; 58 | 59 | if (changeEnd <= tokenStart) // If the change is before the token, shift the and token's start positions. 60 | { 61 | _list[i] = token.Shift(offset); 62 | continue; 63 | } 64 | 65 | // Otherwise, we adjust the token's span. 66 | int newLength; 67 | 68 | if (changeStart <= tokenStart) 69 | { 70 | newLength = changeEnd < tokenEnd 71 | ? tokenEnd - changeStart + offset 72 | : 0; 73 | 74 | _list[i] = token.WithSpan(changeStart, newLength); 75 | continue; 76 | } 77 | 78 | newLength = changeEnd < tokenEnd 79 | ? token.Length + offset 80 | : change.NewLength + changeStart - tokenStart; 81 | 82 | _list[i] = token.WithSpan(tokenStart, newLength); 83 | } 84 | 85 | if (DiagnosticSpan is DiagnosticSpan diagnosticSpan) 86 | { 87 | if (diagnosticSpan.End < changeStart || diagnosticSpan.Start < changeEnd) 88 | return; 89 | 90 | DiagnosticSpan = diagnosticSpan.Shift(offset); 91 | } 92 | } 93 | 94 | public void Clear() 95 | { 96 | _list.Clear(); 97 | DiagnosticSpan = null; 98 | } 99 | 100 | void ISyntaxSpanCollection.AddPunctuator(TextSpan span) 101 | { 102 | VerifyAtEnd(span); 103 | 104 | _list.Add(SyntaxSpan.Create(SyntaxKind.Punctuator, span)); 105 | } 106 | 107 | void ISyntaxSpanCollection.AddOperationName(TextSpan span) 108 | { 109 | VerifyAtEnd(span); 110 | 111 | _list.Add(SyntaxSpan.Create(SyntaxKind.OperationName, span)); 112 | } 113 | 114 | void ISyntaxSpanCollection.AddFragmentName(TextSpan span) 115 | { 116 | VerifyAtEnd(span); 117 | 118 | _list.Add(SyntaxSpan.Create(SyntaxKind.FragmentName, span)); 119 | } 120 | 121 | void ISyntaxSpanCollection.AddKeyword(TextSpan span) 122 | { 123 | VerifyAtEnd(span); 124 | 125 | _list.Add(SyntaxSpan.Create(SyntaxKind.Keyword, span)); 126 | } 127 | 128 | void ISyntaxSpanCollection.AddVariableName(TextSpan span) 129 | { 130 | VerifyAtEnd(span); 131 | 132 | _list.Add(SyntaxSpan.Create(SyntaxKind.VariableName, span)); 133 | } 134 | 135 | void ISyntaxSpanCollection.AddDirectiveName(TextSpan span) 136 | { 137 | VerifyAtEnd(span); 138 | 139 | _list.Add(SyntaxSpan.Create(SyntaxKind.DirectiveName, span)); 140 | } 141 | 142 | void ISyntaxSpanCollection.AddTypeName(TextSpan span) 143 | { 144 | VerifyAtEnd(span); 145 | 146 | _list.Add(SyntaxSpan.Create(SyntaxKind.TypeName, span)); 147 | } 148 | 149 | void ISyntaxSpanCollection.AddFieldName(TextSpan span) 150 | { 151 | VerifyAtEnd(span); 152 | 153 | _list.Add(SyntaxSpan.Create(SyntaxKind.FieldName, span)); 154 | } 155 | 156 | void ISyntaxSpanCollection.AddAliasedFieldName(TextSpan span) 157 | { 158 | VerifyAtEnd(span); 159 | 160 | _list.Add(SyntaxSpan.Create(SyntaxKind.AliasedFieldName, span)); 161 | } 162 | 163 | void ISyntaxSpanCollection.AddArgumentName(TextSpan span) 164 | { 165 | VerifyAtEnd(span); 166 | 167 | _list.Add(SyntaxSpan.Create(SyntaxKind.ArgumentName, span)); 168 | } 169 | 170 | void ISyntaxSpanCollection.AddObjectFieldName(TextSpan span) 171 | { 172 | VerifyAtEnd(span); 173 | 174 | _list.Add(SyntaxSpan.Create(SyntaxKind.ObjectFieldName, span)); 175 | } 176 | 177 | void ISyntaxSpanCollection.AddString(TextSpan span) 178 | { 179 | VerifyAtEnd(span); 180 | 181 | _list.Add(SyntaxSpan.Create(SyntaxKind.String, span)); 182 | } 183 | 184 | void ISyntaxSpanCollection.AddNumber(TextSpan span) 185 | { 186 | VerifyAtEnd(span); 187 | 188 | _list.Add(SyntaxSpan.Create(SyntaxKind.Number, span)); 189 | } 190 | 191 | void ISyntaxSpanCollection.AddEnum(TextSpan span) 192 | { 193 | VerifyAtEnd(span); 194 | 195 | _list.Add(SyntaxSpan.Create(SyntaxKind.Enum, span)); 196 | } 197 | 198 | void ISyntaxSpanCollection.AddName(TextSpan span) 199 | { 200 | VerifyAtEnd(span); 201 | 202 | _list.Add(SyntaxSpan.Create(SyntaxKind.Name, span)); 203 | } 204 | 205 | void ISyntaxSpanCollection.AddComment(TextSpan span) 206 | { 207 | VerifyAtEnd(span); 208 | 209 | _list.Add(SyntaxSpan.Create(SyntaxKind.Comment, span)); 210 | } 211 | 212 | void ISyntaxSpanCollection.AddError(TextSpan span) 213 | { 214 | VerifyAtEnd(span); 215 | 216 | _list.Add(SyntaxSpan.Create(SyntaxKind.Error, span)); 217 | } 218 | 219 | void ISyntaxSpanCollection.SetDiagnostic(TextSpan span, string message) 220 | { 221 | Debug.Assert(DiagnosticSpan == null); 222 | 223 | DiagnosticSpan = Syntax.DiagnosticSpan.Create(message, span); 224 | } 225 | 226 | [Conditional("DEBUG")] 227 | private void VerifyAtEnd(TextSpan span) 228 | { 229 | Debug.Assert(_list.Count == 0 || span.Start >= _list[_list.Count - 1].End, "Attempted to add a tagged span in the wrong order."); 230 | } 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /GraphQLTools/Analysis/SyntaxSpanListPooledObjectPolicy.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.ObjectPool; 2 | 3 | namespace GraphQLTools.Analysis 4 | { 5 | internal class SyntaxSpanListPooledObjectPolicy : IPooledObjectPolicy 6 | { 7 | public static readonly SyntaxSpanListPooledObjectPolicy Instance = new SyntaxSpanListPooledObjectPolicy(); 8 | 9 | private SyntaxSpanListPooledObjectPolicy() { } 10 | 11 | public SyntaxSpanList Create() => new SyntaxSpanList(8); 12 | 13 | public bool Return(SyntaxSpanList obj) 14 | { 15 | lock (obj) 16 | { 17 | obj.Clear(); 18 | } 19 | 20 | if (obj.Count >= 128) 21 | return false; 22 | 23 | return true; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /GraphQLTools/Analysis/SyntaxSpanListSlice.cs: -------------------------------------------------------------------------------- 1 | using GraphQLTools.Syntax; 2 | using Microsoft.VisualStudio.Text; 3 | using System; 4 | using System.Diagnostics; 5 | using System.Threading; 6 | 7 | namespace GraphQLTools.Analysis 8 | { 9 | internal readonly struct SyntaxSpanListSlice 10 | { 11 | private readonly SyntaxSpanList _list; 12 | private readonly Span _span; 13 | 14 | public SyntaxSpanListSlice(SyntaxSpanList list, Span span) 15 | { 16 | _list = list; 17 | _span = span; 18 | } 19 | 20 | public Enumerator GetEnumerator() 21 | { 22 | if (!Monitor.TryEnter(_list, 100)) 23 | return new Enumerator(SyntaxSpanList.EmptySlice._list, false, 0, 0); 24 | 25 | int startIndex = FindStartIndex(); 26 | return new Enumerator(_list, true, startIndex, _span.End); 27 | } 28 | 29 | private int FindStartIndex() 30 | { 31 | int position = _span.Start; 32 | int left = 0; 33 | int right = _list.Count - 1; 34 | int index = _list.Count; 35 | 36 | while (left <= right) 37 | { 38 | int mid = (right + left) / 2; 39 | 40 | if (_list[mid].End >= position) 41 | { 42 | index = mid; 43 | right = mid - 1; 44 | } 45 | else 46 | { 47 | left = mid + 1; 48 | } 49 | } 50 | 51 | return index; 52 | } 53 | 54 | public struct Enumerator : IDisposable 55 | { 56 | private readonly SyntaxSpanList _spans; 57 | private readonly bool _lockTaken; 58 | private readonly int _endPosition; 59 | private int _index; 60 | 61 | public Enumerator(SyntaxSpanList spans, bool lockTaken, int startIndex, int endPosition) 62 | { 63 | _spans = spans; 64 | _lockTaken = lockTaken; 65 | _endPosition = endPosition; 66 | _index = startIndex - 1; 67 | } 68 | 69 | public SyntaxSpan Current 70 | { 71 | get 72 | { 73 | Debug.Assert(_index >= 0 && _index < _spans.Count); 74 | 75 | return _spans[_index]; 76 | } 77 | } 78 | 79 | public void Dispose() 80 | { 81 | if (_lockTaken) 82 | { 83 | Monitor.Exit(_spans); 84 | } 85 | } 86 | 87 | public bool MoveNext() 88 | { 89 | if (_index == _spans.Count - 1) 90 | return false; 91 | 92 | _index++; 93 | 94 | if (_spans[_index].Start >= _endPosition) 95 | { 96 | _index = _spans.Count; 97 | return false; 98 | } 99 | 100 | return true; 101 | } 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /GraphQLTools/Analysis/VerbatimStringLiteralAnalyzedSpan.cs: -------------------------------------------------------------------------------- 1 | using GraphQLTools.Syntax; 2 | using Microsoft.CodeAnalysis.CSharp; 3 | using Microsoft.CodeAnalysis.CSharp.Syntax; 4 | using Microsoft.VisualStudio.Text; 5 | using System.Diagnostics; 6 | using SyntaxKind = Microsoft.CodeAnalysis.CSharp.SyntaxKind; 7 | 8 | namespace GraphQLTools.Analysis 9 | { 10 | internal sealed class VerbatimStringLiteralAnalyzedSpan : AnalyzedSpan 11 | { 12 | public VerbatimStringLiteralAnalyzedSpan(SyntaxKind syntaxKind, bool isUnterminated) 13 | : base(syntaxKind, isUnterminated) 14 | { 15 | } 16 | 17 | protected override int GqlSpanStart => ExpressionSpanStart + 2; 18 | 19 | protected override int GqlSpanEnd => IsUnterminated 20 | ? ExpressionSpanEnd 21 | : ExpressionSpanEnd - 1; 22 | 23 | protected override SourceText CreateSource(ITextSnapshot snapshot) 24 | { 25 | return new VerbatimStringLiteralSourceText(snapshot, GqlSpanStart, GqlSpanEnd); 26 | } 27 | 28 | public static VerbatimStringLiteralAnalyzedSpan Create(ITextSnapshot snapshot, LiteralExpressionSyntax expression) 29 | { 30 | bool isUnterminated = !IsClosingSequence(snapshot, expression.Span.End - 1); 31 | return new VerbatimStringLiteralAnalyzedSpan(expression.Token.Kind(), isUnterminated); 32 | } 33 | 34 | private static bool IsClosingSequence(ITextSnapshot snapshot, int position) 35 | { 36 | if (snapshot[position] != '"') 37 | return false; 38 | 39 | position--; 40 | int doubleQuotesCount = 0; 41 | while (snapshot[position] == '"') 42 | { 43 | doubleQuotesCount++; 44 | position--; 45 | } 46 | 47 | return doubleQuotesCount % 2 == 0; 48 | } 49 | 50 | private sealed class VerbatimStringLiteralSourceText : SnapshotSourceText 51 | { 52 | public VerbatimStringLiteralSourceText(ITextSnapshot snapshot, int start, int end) 53 | : base(snapshot, start, end) 54 | { 55 | } 56 | 57 | protected override char MoveNextCore(ref int position) 58 | { 59 | char current = Snapshot[position]; 60 | position++; 61 | 62 | if (current == '"') 63 | { 64 | Debug.Assert(position != End && Snapshot[position] == '"', "Found unescaped double quotes."); 65 | 66 | position++; 67 | } 68 | 69 | return current; 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /GraphQLTools/Classification/GqlClassificationTypes.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.PlatformUI; 2 | using Microsoft.VisualStudio.Text.Classification; 3 | using Microsoft.VisualStudio.Text.Formatting; 4 | using System; 5 | using System.ComponentModel.Composition; 6 | using System.Windows.Media; 7 | 8 | namespace GraphQLTools.Classification 9 | { 10 | [Export] 11 | internal class GqlClassificationTypes : IDisposable 12 | { 13 | private static bool s_isDarkMode; 14 | 15 | static GqlClassificationTypes() 16 | { 17 | s_isDarkMode = IsDarkMode(); 18 | } 19 | 20 | private readonly IClassificationFormatMapService _formatMap; 21 | private readonly IClassificationTypeRegistryService _typeRegistry; 22 | 23 | [ImportingConstructor] 24 | public GqlClassificationTypes(IClassificationFormatMapService formatMap, IClassificationTypeRegistryService typeRegistry) 25 | { 26 | _formatMap = formatMap; 27 | _typeRegistry = typeRegistry; 28 | 29 | VSColorTheme.ThemeChanged += VSColorTheme_ThemeChanged; 30 | } 31 | 32 | public IClassificationType Punctuator => _typeRegistry.GetClassificationType(GqlPunctuatorFormat.Name); 33 | public IClassificationType Keyword => _typeRegistry.GetClassificationType(GqlKeywordFormat.Name); 34 | public IClassificationType OperationName => _typeRegistry.GetClassificationType(GqlOperationNameFormat.Name); 35 | public IClassificationType FragmentName => _typeRegistry.GetClassificationType(GqlFragmentNameFormat.Name); 36 | public IClassificationType VariableName => _typeRegistry.GetClassificationType(GqlVariableNameFormat.Name); 37 | public IClassificationType DirectiveName => _typeRegistry.GetClassificationType(GqlDirectiveNameFormat.Name); 38 | public IClassificationType TypeName => _typeRegistry.GetClassificationType(GqlTypeNameFormat.Name); 39 | public IClassificationType FieldName => _typeRegistry.GetClassificationType(GqlFieldNameFormat.Name); 40 | public IClassificationType AliasedFieldName => _typeRegistry.GetClassificationType(GqlAliasedFieldNameFormat.Name); 41 | public IClassificationType ArgumentName => _typeRegistry.GetClassificationType(GqlArgumentNameFormat.Name); 42 | public IClassificationType ObjectFieldName => _typeRegistry.GetClassificationType(GqlObjectFieldNameFormat.Name); 43 | public IClassificationType String => _typeRegistry.GetClassificationType(GqlStringFormat.Name); 44 | public IClassificationType Number => _typeRegistry.GetClassificationType(GqlNumberFormat.Name); 45 | public IClassificationType Enum => _typeRegistry.GetClassificationType(GqlEnumFormat.Name); 46 | public IClassificationType Name => _typeRegistry.GetClassificationType(GqlNameFormat.Name); 47 | public IClassificationType Comment => _typeRegistry.GetClassificationType(GqlCommentFormat.Name); 48 | public IClassificationType Error => _typeRegistry.GetClassificationType(GqlErrorFormat.Name); 49 | 50 | private void VSColorTheme_ThemeChanged(ThemeChangedEventArgs e) 51 | { 52 | s_isDarkMode = IsDarkMode(); 53 | IClassificationFormatMap formatMap = _formatMap.GetClassificationFormatMap("text"); 54 | 55 | TextFormattingRunProperties properties; 56 | formatMap.BeginBatchUpdate(); 57 | try 58 | { 59 | properties = formatMap.GetTextProperties(Punctuator); 60 | formatMap.SetTextProperties(Punctuator, properties.SetForegroundBrush(new SolidColorBrush(GqlPunctuatorFormat.Color))); 61 | 62 | properties = formatMap.GetTextProperties(OperationName); 63 | formatMap.SetTextProperties(OperationName, properties.SetForegroundBrush(new SolidColorBrush(GqlOperationNameFormat.Color))); 64 | 65 | properties = formatMap.GetTextProperties(Keyword); 66 | formatMap.SetTextProperties(Keyword, properties.SetForegroundBrush(new SolidColorBrush(GqlKeywordFormat.Color))); 67 | 68 | properties = formatMap.GetTextProperties(FragmentName); 69 | formatMap.SetTextProperties(FragmentName, properties.SetForegroundBrush(new SolidColorBrush(GqlFragmentNameFormat.Color))); 70 | 71 | properties = formatMap.GetTextProperties(VariableName); 72 | formatMap.SetTextProperties(VariableName, properties.SetForegroundBrush(new SolidColorBrush(GqlVariableNameFormat.Color))); 73 | 74 | properties = formatMap.GetTextProperties(DirectiveName); 75 | formatMap.SetTextProperties(DirectiveName, properties.SetForegroundBrush(new SolidColorBrush(GqlDirectiveNameFormat.Color))); 76 | 77 | properties = formatMap.GetTextProperties(TypeName); 78 | formatMap.SetTextProperties(TypeName, properties.SetForegroundBrush(new SolidColorBrush(GqlTypeNameFormat.Color))); 79 | 80 | properties = formatMap.GetTextProperties(FieldName); 81 | formatMap.SetTextProperties(FieldName, properties.SetForegroundBrush(new SolidColorBrush(GqlFieldNameFormat.Color))); 82 | 83 | properties = formatMap.GetTextProperties(AliasedFieldName); 84 | formatMap.SetTextProperties(AliasedFieldName, properties.SetForegroundBrush(new SolidColorBrush(GqlAliasedFieldNameFormat.Color))); 85 | 86 | properties = formatMap.GetTextProperties(ArgumentName); 87 | formatMap.SetTextProperties(ArgumentName, properties.SetForegroundBrush(new SolidColorBrush(GqlArgumentNameFormat.Color))); 88 | 89 | properties = formatMap.GetTextProperties(ObjectFieldName); 90 | formatMap.SetTextProperties(ObjectFieldName, properties.SetForegroundBrush(new SolidColorBrush(GqlObjectFieldNameFormat.Color))); 91 | 92 | properties = formatMap.GetTextProperties(String); 93 | formatMap.SetTextProperties(String, properties.SetForegroundBrush(new SolidColorBrush(GqlStringFormat.Color))); 94 | 95 | properties = formatMap.GetTextProperties(Number); 96 | formatMap.SetTextProperties(Number, properties.SetForegroundBrush(new SolidColorBrush(GqlNumberFormat.Color))); 97 | 98 | properties = formatMap.GetTextProperties(Enum); 99 | formatMap.SetTextProperties(Enum, properties.SetForegroundBrush(new SolidColorBrush(GqlEnumFormat.Color))); 100 | 101 | properties = formatMap.GetTextProperties(Name); 102 | formatMap.SetTextProperties(Name, properties.SetForegroundBrush(new SolidColorBrush(GqlNameFormat.Color))); 103 | 104 | properties = formatMap.GetTextProperties(Comment); 105 | formatMap.SetTextProperties(Comment, properties.SetForegroundBrush(new SolidColorBrush(GqlCommentFormat.Color))); 106 | 107 | properties = formatMap.GetTextProperties(Error); 108 | formatMap.SetTextProperties(Error, properties.SetForegroundBrush(new SolidColorBrush(GqlErrorFormat.Color))); 109 | } 110 | finally 111 | { 112 | formatMap.EndBatchUpdate(); 113 | } 114 | } 115 | 116 | public void Dispose() 117 | { 118 | VSColorTheme.ThemeChanged -= VSColorTheme_ThemeChanged; 119 | } 120 | 121 | public static ref readonly Color GetThemeAwareColor(in Color lightModeColor, in Color darkModeColor) => ref s_isDarkMode ? ref darkModeColor : ref lightModeColor; 122 | 123 | private static bool IsDarkMode() => VSColorTheme.GetThemedColor(EnvironmentColors.ToolWindowBackgroundColorKey).GetBrightness() < 0.5; 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /GraphQLTools/Classification/GqlClassifierClassificationDefinition.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.Text.Classification; 2 | using Microsoft.VisualStudio.Utilities; 3 | using System.ComponentModel.Composition; 4 | 5 | namespace GraphQLTools.Classification 6 | { 7 | internal static class GqlClassifierClassificationDefinition 8 | { 9 | [Export(typeof(ClassificationTypeDefinition))] 10 | [Name(GqlPunctuatorFormat.Name)] 11 | public static ClassificationTypeDefinition PunctuatorDefinition; 12 | 13 | [Export(typeof(ClassificationTypeDefinition))] 14 | [Name(GqlKeywordFormat.Name)] 15 | public static ClassificationTypeDefinition KeywordDefinition; 16 | 17 | [Export(typeof(ClassificationTypeDefinition))] 18 | [Name(GqlOperationNameFormat.Name)] 19 | public static ClassificationTypeDefinition OperationNameDefinition; 20 | 21 | [Export(typeof(ClassificationTypeDefinition))] 22 | [Name(GqlFragmentNameFormat.Name)] 23 | public static ClassificationTypeDefinition FragmentNameDefinition; 24 | 25 | [Export(typeof(ClassificationTypeDefinition))] 26 | [Name(GqlVariableNameFormat.Name)] 27 | public static ClassificationTypeDefinition VariableNameDefinition; 28 | 29 | [Export(typeof(ClassificationTypeDefinition))] 30 | [Name(GqlDirectiveNameFormat.Name)] 31 | public static ClassificationTypeDefinition DirectiveNameDefinition; 32 | 33 | [Export(typeof(ClassificationTypeDefinition))] 34 | [Name(GqlTypeNameFormat.Name)] 35 | public static ClassificationTypeDefinition TypeNameDefinition; 36 | 37 | [Export(typeof(ClassificationTypeDefinition))] 38 | [Name(GqlFieldNameFormat.Name)] 39 | public static ClassificationTypeDefinition FieldNameDefinition; 40 | 41 | [Export(typeof(ClassificationTypeDefinition))] 42 | [Name(GqlAliasedFieldNameFormat.Name)] 43 | public static ClassificationTypeDefinition AliasedFieldNameDefinition; 44 | 45 | [Export(typeof(ClassificationTypeDefinition))] 46 | [Name(GqlArgumentNameFormat.Name)] 47 | public static ClassificationTypeDefinition ArgumentNameDefinition; 48 | 49 | [Export(typeof(ClassificationTypeDefinition))] 50 | [Name(GqlObjectFieldNameFormat.Name)] 51 | public static ClassificationTypeDefinition ObjectFieldNameDefinition; 52 | 53 | [Export(typeof(ClassificationTypeDefinition))] 54 | [Name(GqlStringFormat.Name)] 55 | public static ClassificationTypeDefinition StringDefinition; 56 | 57 | [Export(typeof(ClassificationTypeDefinition))] 58 | [Name(GqlNumberFormat.Name)] 59 | public static ClassificationTypeDefinition NumberDefinition; 60 | 61 | [Export(typeof(ClassificationTypeDefinition))] 62 | [Name(GqlEnumFormat.Name)] 63 | public static ClassificationTypeDefinition EnumDefinition; 64 | 65 | [Export(typeof(ClassificationTypeDefinition))] 66 | [Name(GqlNameFormat.Name)] 67 | public static ClassificationTypeDefinition NameDefinition; 68 | 69 | [Export(typeof(ClassificationTypeDefinition))] 70 | [Name(GqlCommentFormat.Name)] 71 | public static ClassificationTypeDefinition CommentDefinition; 72 | 73 | [Export(typeof(ClassificationTypeDefinition))] 74 | [Name(GqlErrorFormat.Name)] 75 | public static ClassificationTypeDefinition ErrorDefinition; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /GraphQLTools/Classification/GqlClassifierFormats.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.Text.Classification; 2 | using Microsoft.VisualStudio.Utilities; 3 | using System.ComponentModel.Composition; 4 | using System.Windows.Media; 5 | 6 | namespace GraphQLTools.Classification 7 | { 8 | internal abstract class GqlClassificationFormatDefinition : ClassificationFormatDefinition 9 | { 10 | public GqlClassificationFormatDefinition() 11 | { 12 | DisplayName = NameCore; 13 | ForegroundColor = ColorCore; 14 | } 15 | 16 | protected abstract string NameCore { get; } 17 | 18 | protected abstract Color ColorCore { get; } 19 | } 20 | 21 | [Export(typeof(EditorFormatDefinition))] 22 | [ClassificationType(ClassificationTypeNames = Name)] 23 | [Name(Name)] 24 | [UserVisible(true)] 25 | [Order(After = Priority.High, Before = Priority.High)] 26 | internal sealed class GqlPunctuatorFormat : GqlClassificationFormatDefinition 27 | { 28 | public const string Name = "GraphQL - Punctuator"; 29 | 30 | private static readonly Color s_lightModeColor = Color.FromRgb(20, 20, 20); 31 | private static readonly Color s_darkModeColor = Color.FromRgb(221, 221, 221); 32 | public static ref readonly Color Color => ref GqlClassificationTypes.GetThemeAwareColor(in s_lightModeColor, in s_darkModeColor); 33 | 34 | protected override string NameCore => Name; 35 | 36 | protected override Color ColorCore => Color; 37 | } 38 | 39 | [Export(typeof(EditorFormatDefinition))] 40 | [ClassificationType(ClassificationTypeNames = Name)] 41 | [Name(Name)] 42 | [UserVisible(true)] 43 | [Order(After = Priority.High, Before = Priority.High)] 44 | internal sealed class GqlKeywordFormat : GqlClassificationFormatDefinition 45 | { 46 | public const string Name = "GraphQL - Keyword"; 47 | 48 | private static readonly Color s_lightModeColor = Color.FromRgb(177, 26, 4); 49 | private static readonly Color s_darkModeColor = Color.FromRgb(74, 162, 225); 50 | public static ref readonly Color Color => ref GqlClassificationTypes.GetThemeAwareColor(in s_lightModeColor, in s_darkModeColor); 51 | 52 | protected override string NameCore => Name; 53 | 54 | protected override Color ColorCore => Color; 55 | } 56 | 57 | [Export(typeof(EditorFormatDefinition))] 58 | [ClassificationType(ClassificationTypeNames = Name)] 59 | [Name(Name)] 60 | [UserVisible(true)] 61 | [Order(After = Priority.High, Before = Priority.High)] 62 | internal sealed class GqlOperationNameFormat : GqlClassificationFormatDefinition 63 | { 64 | public const string Name = "GraphQL - Operation name"; 65 | 66 | private static readonly Color s_lightModeColor = Color.FromRgb(210, 5, 78); 67 | private static readonly Color s_darkModeColor = Color.FromRgb(252, 222, 143); 68 | public static ref readonly Color Color => ref GqlClassificationTypes.GetThemeAwareColor(in s_lightModeColor, in s_darkModeColor); 69 | 70 | protected override string NameCore => Name; 71 | 72 | protected override Color ColorCore => Color; 73 | } 74 | 75 | [Export(typeof(EditorFormatDefinition))] 76 | [ClassificationType(ClassificationTypeNames = Name)] 77 | [Name(Name)] 78 | [UserVisible(true)] 79 | [Order(After = Priority.High, Before = Priority.High)] 80 | internal sealed class GqlFragmentNameFormat : GqlClassificationFormatDefinition 81 | { 82 | public const string Name = "GraphQL - Fragment name"; 83 | 84 | private static readonly Color s_lightModeColor = Color.FromRgb(31, 209, 214); 85 | private static readonly Color s_darkModeColor = Color.FromRgb(217, 251, 145); 86 | public static ref readonly Color Color => ref GqlClassificationTypes.GetThemeAwareColor(in s_lightModeColor, in s_darkModeColor); 87 | 88 | protected override string NameCore => Name; 89 | 90 | protected override Color ColorCore => Color; 91 | } 92 | 93 | [Export(typeof(EditorFormatDefinition))] 94 | [ClassificationType(ClassificationTypeNames = Name)] 95 | [Name(Name)] 96 | [UserVisible(true)] 97 | [Order(After = Priority.High, Before = Priority.High)] 98 | internal sealed class GqlVariableNameFormat : GqlClassificationFormatDefinition 99 | { 100 | public const string Name = "GraphQL - Variable name"; 101 | 102 | private static readonly Color s_lightModeColor = Color.FromRgb(57, 125, 19); 103 | private static readonly Color s_darkModeColor = Color.FromRgb(123, 219, 36); 104 | public static ref readonly Color Color => ref GqlClassificationTypes.GetThemeAwareColor(in s_lightModeColor, in s_darkModeColor); 105 | 106 | protected override string NameCore => Name; 107 | 108 | protected override Color ColorCore => Color; 109 | } 110 | 111 | [Export(typeof(EditorFormatDefinition))] 112 | [ClassificationType(ClassificationTypeNames = Name)] 113 | [Name(Name)] 114 | [UserVisible(true)] 115 | [Order(After = Priority.High, Before = Priority.High)] 116 | internal sealed class GqlDirectiveNameFormat : GqlClassificationFormatDefinition 117 | { 118 | public const string Name = "GraphQL - Directive name"; 119 | 120 | private static readonly Color s_lightModeColor = Color.FromRgb(179, 48, 134); 121 | private static readonly Color s_darkModeColor = Color.FromRgb(224, 98, 212); 122 | public static ref readonly Color Color => ref GqlClassificationTypes.GetThemeAwareColor(in s_lightModeColor, in s_darkModeColor); 123 | 124 | protected override string NameCore => Name; 125 | 126 | protected override Color ColorCore => Color; 127 | } 128 | 129 | [Export(typeof(EditorFormatDefinition))] 130 | [ClassificationType(ClassificationTypeNames = Name)] 131 | [Name(Name)] 132 | [UserVisible(true)] 133 | [Order(After = Priority.High, Before = Priority.High)] 134 | internal sealed class GqlTypeNameFormat : GqlClassificationFormatDefinition 135 | { 136 | public const string Name = "GraphQL - Type name"; 137 | 138 | private static readonly Color s_lightModeColor = Color.FromRgb(202, 152, 0); 139 | private static readonly Color s_darkModeColor = Color.FromRgb(80, 240, 190); 140 | public static ref readonly Color Color => ref GqlClassificationTypes.GetThemeAwareColor(in s_lightModeColor, in s_darkModeColor); 141 | 142 | protected override string NameCore => Name; 143 | 144 | protected override Color ColorCore => Color; 145 | } 146 | 147 | [Export(typeof(EditorFormatDefinition))] 148 | [ClassificationType(ClassificationTypeNames = Name)] 149 | [Name(Name)] 150 | [UserVisible(true)] 151 | [Order(After = Priority.High, Before = Priority.High)] 152 | internal sealed class GqlFieldNameFormat : GqlClassificationFormatDefinition 153 | { 154 | public const string Name = "GraphQL - Field name"; 155 | 156 | private static readonly Color s_lightModeColor = Color.FromRgb(31, 97, 160); 157 | private static readonly Color s_darkModeColor = Color.FromRgb(121, 164, 171); 158 | public static ref readonly Color Color => ref GqlClassificationTypes.GetThemeAwareColor(in s_lightModeColor, in s_darkModeColor); 159 | 160 | protected override string NameCore => Name; 161 | 162 | protected override Color ColorCore => Color; 163 | } 164 | 165 | [Export(typeof(EditorFormatDefinition))] 166 | [ClassificationType(ClassificationTypeNames = Name)] 167 | [Name(Name)] 168 | [UserVisible(true)] 169 | [Order(After = Priority.High, Before = Priority.High)] 170 | internal sealed class GqlAliasedFieldNameFormat : GqlClassificationFormatDefinition 171 | { 172 | public const string Name = "GraphQL - Aliased field name"; 173 | 174 | private static readonly Color s_lightModeColor = Color.FromRgb(28, 146, 169); 175 | private static readonly Color s_darkModeColor = Color.FromRgb(96, 170, 137); 176 | public static ref readonly Color Color => ref GqlClassificationTypes.GetThemeAwareColor(in s_lightModeColor, in s_darkModeColor); 177 | 178 | protected override string NameCore => Name; 179 | 180 | protected override Color ColorCore => Color; 181 | } 182 | 183 | [Export(typeof(EditorFormatDefinition))] 184 | [ClassificationType(ClassificationTypeNames = Name)] 185 | [Name(Name)] 186 | [UserVisible(true)] 187 | [Order(After = Priority.High, Before = Priority.High)] 188 | internal sealed class GqlArgumentNameFormat : GqlClassificationFormatDefinition 189 | { 190 | public const string Name = "GraphQL - Argument name"; 191 | 192 | private static readonly Color s_lightModeColor = Color.FromRgb(139, 43, 185); 193 | private static readonly Color s_darkModeColor = Color.FromRgb(195, 255, 255); 194 | public static ref readonly Color Color => ref GqlClassificationTypes.GetThemeAwareColor(in s_lightModeColor, in s_darkModeColor); 195 | 196 | protected override string NameCore => Name; 197 | 198 | protected override Color ColorCore => Color; 199 | } 200 | 201 | [Export(typeof(EditorFormatDefinition))] 202 | [ClassificationType(ClassificationTypeNames = Name)] 203 | [Name(Name)] 204 | [UserVisible(true)] 205 | [Order(After = Priority.High, Before = Priority.High)] 206 | internal sealed class GqlObjectFieldNameFormat : GqlClassificationFormatDefinition 207 | { 208 | public const string Name = "GraphQL - Object field name"; 209 | 210 | private static readonly Color s_lightModeColor = Color.FromRgb(168, 130, 108); 211 | private static readonly Color s_darkModeColor = Color.FromRgb(218, 197, 216); 212 | public static ref readonly Color Color => ref GqlClassificationTypes.GetThemeAwareColor(in s_lightModeColor, in s_darkModeColor); 213 | 214 | protected override string NameCore => Name; 215 | 216 | protected override Color ColorCore => Color; 217 | } 218 | 219 | [Export(typeof(EditorFormatDefinition))] 220 | [ClassificationType(ClassificationTypeNames = Name)] 221 | [Name(Name)] 222 | [UserVisible(true)] 223 | [Order(After = Priority.High, Before = Priority.High)] 224 | internal sealed class GqlStringFormat : GqlClassificationFormatDefinition 225 | { 226 | public const string Name = "GraphQL - String"; 227 | 228 | private static readonly Color s_lightModeColor = Color.FromRgb(214, 66, 146); 229 | private static readonly Color s_darkModeColor = Color.FromRgb(227, 211, 180); 230 | public static ref readonly Color Color => ref GqlClassificationTypes.GetThemeAwareColor(in s_lightModeColor, in s_darkModeColor); 231 | 232 | protected override string NameCore => Name; 233 | 234 | protected override Color ColorCore => Color; 235 | } 236 | 237 | [Export(typeof(EditorFormatDefinition))] 238 | [ClassificationType(ClassificationTypeNames = Name)] 239 | [Name(Name)] 240 | [UserVisible(true)] 241 | [Order(After = Priority.High, Before = Priority.High)] 242 | internal sealed class GqlNumberFormat : GqlClassificationFormatDefinition 243 | { 244 | public const string Name = "GraphQL - Number"; 245 | 246 | private static readonly Color s_lightModeColor = Color.FromRgb(40, 130, 249); 247 | private static readonly Color s_darkModeColor = Color.FromRgb(181, 206, 168); 248 | public static ref readonly Color Color => ref GqlClassificationTypes.GetThemeAwareColor(in s_lightModeColor, in s_darkModeColor); 249 | 250 | protected override string NameCore => Name; 251 | 252 | protected override Color ColorCore => Color; 253 | } 254 | 255 | [Export(typeof(EditorFormatDefinition))] 256 | [ClassificationType(ClassificationTypeNames = Name)] 257 | [Name(Name)] 258 | [UserVisible(true)] 259 | [Order(After = Priority.High, Before = Priority.High)] 260 | internal sealed class GqlEnumFormat : GqlClassificationFormatDefinition 261 | { 262 | public const string Name = "GraphQL - Enum"; 263 | 264 | private static readonly Color s_lightModeColor = Color.FromRgb(140, 200, 20); 265 | private static readonly Color s_darkModeColor = Color.FromRgb(190, 183, 255); 266 | public static ref readonly Color Color => ref GqlClassificationTypes.GetThemeAwareColor(in s_lightModeColor, in s_darkModeColor); 267 | 268 | protected override string NameCore => Name; 269 | 270 | protected override Color ColorCore => Color; 271 | } 272 | 273 | [Export(typeof(EditorFormatDefinition))] 274 | [ClassificationType(ClassificationTypeNames = Name)] 275 | [Name(Name)] 276 | [UserVisible(true)] 277 | [Order(After = Priority.High, Before = Priority.High)] 278 | internal sealed class GqlNameFormat : GqlClassificationFormatDefinition 279 | { 280 | public const string Name = "GraphQL - Name (unclassified)"; 281 | 282 | private static readonly Color s_lightModeColor = Color.FromRgb(20, 20, 20); 283 | private static readonly Color s_darkModeColor = Color.FromRgb(192, 192, 192); 284 | public static ref readonly Color Color => ref GqlClassificationTypes.GetThemeAwareColor(in s_lightModeColor, in s_darkModeColor); 285 | 286 | protected override string NameCore => Name; 287 | 288 | protected override Color ColorCore => Color; 289 | } 290 | 291 | [Export(typeof(EditorFormatDefinition))] 292 | [ClassificationType(ClassificationTypeNames = Name)] 293 | [Name(Name)] 294 | [UserVisible(true)] 295 | [Order(After = Priority.High, Before = Priority.High)] 296 | internal sealed class GqlCommentFormat : GqlClassificationFormatDefinition 297 | { 298 | public const string Name = "GraphQL - Comment"; 299 | 300 | private static readonly Color s_lightModeColor = Color.FromRgb(157, 157, 157); 301 | private static readonly Color s_darkModeColor = Color.FromRgb(157, 157, 157); 302 | public static ref readonly Color Color => ref GqlClassificationTypes.GetThemeAwareColor(in s_lightModeColor, in s_darkModeColor); 303 | 304 | protected override string NameCore => Name; 305 | 306 | protected override Color ColorCore => Color; 307 | } 308 | 309 | [Export(typeof(EditorFormatDefinition))] 310 | [ClassificationType(ClassificationTypeNames = Name)] 311 | [Name(Name)] 312 | [UserVisible(true)] 313 | [Order(After = Priority.High, Before = Priority.High)] 314 | internal sealed class GqlErrorFormat : GqlClassificationFormatDefinition 315 | { 316 | public const string Name = "GraphQL - Error"; 317 | 318 | private static readonly Color s_lightModeColor = Color.FromRgb(200, 0, 0); 319 | private static readonly Color s_darkModeColor = Color.FromRgb(230, 0, 0); 320 | public static ref readonly Color Color => ref GqlClassificationTypes.GetThemeAwareColor(in s_lightModeColor, in s_darkModeColor); 321 | 322 | protected override string NameCore => Name; 323 | 324 | protected override Color ColorCore => Color; 325 | } 326 | } 327 | -------------------------------------------------------------------------------- /GraphQLTools/GraphQLTools.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 17.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | Debug 10 | AnyCPU 11 | 2.0 12 | {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 13 | {82156AF4-FCBC-4902-B6C5-E29D402BF0E4} 14 | Library 15 | Properties 16 | GraphQLTools 17 | GraphQLTools 18 | v4.7.2 19 | true 20 | true 21 | true 22 | false 23 | false 24 | true 25 | true 26 | Program 27 | $(DevEnvDir)devenv.exe 28 | /rootsuffix Exp 29 | 30 | 31 | true 32 | full 33 | false 34 | bin\Debug\ 35 | DEBUG;TRACE 36 | prompt 37 | 4 38 | 39 | 40 | pdbonly 41 | true 42 | bin\Release\ 43 | TRACE 44 | prompt 45 | 4 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | Designer 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 4.2.0 81 | 82 | 83 | 7.0.12 84 | 85 | 86 | 4.2.0 87 | 88 | 89 | compile; build; native; contentfiles; analyzers; buildtransitive 90 | 91 | 92 | runtime; build; native; contentfiles; analyzers; buildtransitive 93 | all 94 | 95 | 96 | 97 | 98 | Always 99 | true 100 | 101 | 102 | true 103 | Always 104 | 105 | 106 | 107 | 108 | {b011be80-7b51-4f44-9c9d-ce06f4548572} 109 | GraphQLTools.Core 110 | 111 | 112 | 113 | 114 | 121 | -------------------------------------------------------------------------------- /GraphQLTools/GraphQLToolsPackage.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.Shell; 2 | using System; 3 | using System.Runtime.InteropServices; 4 | using System.Threading; 5 | using Task = System.Threading.Tasks.Task; 6 | 7 | namespace GraphQLTools 8 | { 9 | /// 10 | /// This is the class that implements the package exposed by this assembly. 11 | /// 12 | /// 13 | /// 14 | /// The minimum requirement for a class to be considered a valid package for Visual Studio 15 | /// is to implement the IVsPackage interface and register itself with the shell. 16 | /// This package uses the helper classes defined inside the Managed Package Framework (MPF) 17 | /// to do it: it derives from the Package class that provides the implementation of the 18 | /// IVsPackage interface and uses the registration attributes defined in the framework to 19 | /// register itself and its components with the shell. These attributes tell the pkgdef creation 20 | /// utility what data to put into .pkgdef file. 21 | /// 22 | /// 23 | /// To get loaded into VS, the package must be referred by <Asset Type="Microsoft.VisualStudio.VsPackage" ...> in .vsixmanifest file. 24 | /// 25 | /// 26 | [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)] 27 | [Guid(GraphQLToolsPackage.PackageGuidString)] 28 | public sealed class GraphQLToolsPackage : AsyncPackage 29 | { 30 | /// 31 | /// GraphQLToolsPackage GUID string. 32 | /// 33 | public const string PackageGuidString = "6eb9555c-0082-4f37-8817-1459bcb4c60d"; 34 | 35 | #region Package Members 36 | 37 | /// 38 | /// Initialization of the package; this method is called right after the package is sited, so this is the place 39 | /// where you can put all the initialization code that rely on services provided by VisualStudio. 40 | /// 41 | /// A cancellation token to monitor for initialization cancellation, which can occur when VS is shutting down. 42 | /// A provider for progress updates. 43 | /// A task representing the async work of package initialization, or an already completed task if there is none. Do not return null from this method. 44 | protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress progress) 45 | { 46 | // When initialized asynchronously, the current thread may be a background thread at this point. 47 | // Do any initialization that requires the UI thread after switching to the UI thread. 48 | await this.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); 49 | } 50 | 51 | #endregion 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /GraphQLTools/LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Code Architects 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /GraphQLTools/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: AssemblyTitle("GraphQLTools")] 9 | [assembly: AssemblyDescription("GraphQL syntax highlighting support for StringSyntaxAttribute.")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Code Architects")] 12 | [assembly: AssemblyProduct("GraphQLTools")] 13 | [assembly: AssemblyCopyright("")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // Version information for an assembly consists of the following four values: 23 | // 24 | // Major Version 25 | // Minor Version 26 | // Build Number 27 | // Revision 28 | // 29 | // You can specify all the values or you can default the Build and Revision Numbers 30 | // by using the '*' as shown below: 31 | // [assembly: AssemblyVersion("1.0.*")] 32 | [assembly: AssemblyVersion("1.0.0.0")] 33 | [assembly: AssemblyFileVersion("1.0.0.0")] 34 | 35 | [assembly: InternalsVisibleTo("GraphQLTools.Tests")] 36 | [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] 37 | -------------------------------------------------------------------------------- /GraphQLTools/Resources/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codearchitects/GraphQLTools/77acda10c4c8e9180056729f5b2801430ddbb104/GraphQLTools/Resources/logo.png -------------------------------------------------------------------------------- /GraphQLTools/Tagging/GqlTagSpanFactory.cs: -------------------------------------------------------------------------------- 1 | using GraphQLTools.Classification; 2 | using GraphQLTools.Syntax; 3 | using Microsoft.VisualStudio.Text; 4 | using Microsoft.VisualStudio.Text.Adornments; 5 | using Microsoft.VisualStudio.Text.Tagging; 6 | using System; 7 | 8 | namespace GraphQLTools.Tagging 9 | { 10 | internal sealed class GqlTagSpanFactory 11 | { 12 | private readonly ClassificationTag _punctuator; 13 | private readonly ClassificationTag _keyword; 14 | private readonly ClassificationTag _operationName; 15 | private readonly ClassificationTag _fragmentName; 16 | private readonly ClassificationTag _variableName; 17 | private readonly ClassificationTag _directiveName; 18 | private readonly ClassificationTag _typeName; 19 | private readonly ClassificationTag _fieldName; 20 | private readonly ClassificationTag _aliasedFieldName; 21 | private readonly ClassificationTag _argumentName; 22 | private readonly ClassificationTag _objectFieldName; 23 | private readonly ClassificationTag _string; 24 | private readonly ClassificationTag _number; 25 | private readonly ClassificationTag _enum; 26 | private readonly ClassificationTag _name; 27 | private readonly ClassificationTag _comment; 28 | private readonly ClassificationTag _error; 29 | 30 | public GqlTagSpanFactory(GqlClassificationTypes classificationTypes) 31 | { 32 | _punctuator = new ClassificationTag(classificationTypes.Punctuator); 33 | _keyword = new ClassificationTag(classificationTypes.Keyword); 34 | _operationName = new ClassificationTag(classificationTypes.OperationName); 35 | _fragmentName = new ClassificationTag(classificationTypes.FragmentName); 36 | _variableName = new ClassificationTag(classificationTypes.VariableName); 37 | _directiveName = new ClassificationTag(classificationTypes.DirectiveName); 38 | _typeName = new ClassificationTag(classificationTypes.TypeName); 39 | _fieldName = new ClassificationTag(classificationTypes.FieldName); 40 | _aliasedFieldName = new ClassificationTag(classificationTypes.AliasedFieldName); 41 | _argumentName = new ClassificationTag(classificationTypes.ArgumentName); 42 | _objectFieldName = new ClassificationTag(classificationTypes.ObjectFieldName); 43 | _string = new ClassificationTag(classificationTypes.String); 44 | _number = new ClassificationTag(classificationTypes.Number); 45 | _enum = new ClassificationTag(classificationTypes.Enum); 46 | _name = new ClassificationTag(classificationTypes.Name); 47 | _comment = new ClassificationTag(classificationTypes.Comment); 48 | _error = new ClassificationTag(classificationTypes.Error); 49 | } 50 | 51 | public ITagSpan CreateTagSpan(ITextSnapshot snapshot, in SyntaxSpan span) 52 | { 53 | IClassificationTag tag = GetClassificationTag(span.Kind); 54 | return new TagSpan(new SnapshotSpan(snapshot, new Span(span.Start, span.Length)), tag); 55 | } 56 | 57 | public ITagSpan CreateTagSpan(ITextSnapshot snapshot, in DiagnosticSpan span) 58 | { 59 | ErrorTag tag = new ErrorTag(PredefinedErrorTypeNames.OtherError, span.Message); 60 | return new TagSpan(new SnapshotSpan(snapshot, new Span(span.Start, span.Length)), tag); 61 | } 62 | 63 | private IClassificationTag GetClassificationTag(SyntaxKind kind) 64 | { 65 | switch (kind) 66 | { 67 | case SyntaxKind.Punctuator: 68 | return _punctuator; 69 | 70 | case SyntaxKind.Keyword: 71 | return _keyword; 72 | 73 | case SyntaxKind.OperationName: 74 | return _operationName; 75 | 76 | case SyntaxKind.FragmentName: 77 | return _fragmentName; 78 | 79 | case SyntaxKind.VariableName: 80 | return _variableName; 81 | 82 | case SyntaxKind.DirectiveName: 83 | return _directiveName; 84 | 85 | case SyntaxKind.TypeName: 86 | return _typeName; 87 | 88 | case SyntaxKind.FieldName: 89 | return _fieldName; 90 | 91 | case SyntaxKind.AliasedFieldName: 92 | return _aliasedFieldName; 93 | 94 | case SyntaxKind.ArgumentName: 95 | return _argumentName; 96 | 97 | case SyntaxKind.ObjectFieldName: 98 | return _objectFieldName; 99 | 100 | case SyntaxKind.String: 101 | return _string; 102 | 103 | case SyntaxKind.Number: 104 | return _number; 105 | 106 | case SyntaxKind.Enum: 107 | return _enum; 108 | 109 | case SyntaxKind.Name: 110 | return _name; 111 | 112 | case SyntaxKind.Comment: 113 | return _comment; 114 | 115 | case SyntaxKind.Error: 116 | return _error; 117 | 118 | default: 119 | throw new ArgumentException($"Invalid {nameof(SyntaxKind)}.", nameof(kind)); 120 | } 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /GraphQLTools/Tagging/GqlTagger.cs: -------------------------------------------------------------------------------- 1 | using GraphQLTools.Analysis; 2 | using GraphQLTools.Syntax; 3 | using Microsoft.CodeAnalysis; 4 | using Microsoft.CodeAnalysis.Text; 5 | using Microsoft.Extensions.ObjectPool; 6 | using Microsoft.VisualStudio.Shell; 7 | using Microsoft.VisualStudio.Text; 8 | using Microsoft.VisualStudio.Text.Tagging; 9 | using System; 10 | using System.Collections.Generic; 11 | using System.Diagnostics; 12 | using System.Threading; 13 | using System.Threading.Tasks; 14 | 15 | namespace GraphQLTools.Tagging 16 | { 17 | internal class GqlTagger : ITagger, ITagger 18 | { 19 | private readonly ITextBuffer2 _buffer; 20 | private readonly AnalyzedSpanBag _analyzedSpans; 21 | private readonly GqlTagSpanFactory _tagSpanFactory; 22 | private readonly TaggerCancellationTokenSource _cts; 23 | 24 | public GqlTagger(ITextBuffer2 buffer, GqlTagSpanFactory tagSpanFactory, ObjectPool spanListPool) 25 | { 26 | _buffer = buffer; 27 | _tagSpanFactory = tagSpanFactory; 28 | _analyzedSpans = AnalyzedSpanBag.Create(spanListPool); 29 | _cts = TaggerCancellationTokenSource.Create(); 30 | 31 | buffer.Changed += Buffer_Changed; 32 | buffer.ChangedOnBackground += Buffer_ChangedOnBackground; 33 | } 34 | 35 | public event EventHandler TagsChanged; 36 | 37 | public void Close() 38 | { 39 | _cts.Clear(); 40 | _analyzedSpans.Clear(); 41 | 42 | _buffer.Changed -= Buffer_Changed; 43 | _buffer.ChangedOnBackground -= Buffer_ChangedOnBackground; 44 | } 45 | 46 | public void ScanDocument(ProjectId projectId = null) 47 | { 48 | ITextSnapshot snapshot = _buffer.CurrentSnapshot; 49 | Document document = snapshot.GetOpenDocumentInCurrentContextWithChanges(); 50 | if (projectId != null && document.Project.Id != projectId) 51 | return; 52 | 53 | try 54 | { 55 | ThreadHelper.JoinableTaskFactory.Run(() => ScanDocumentAsync(snapshot)); 56 | } 57 | catch (OperationCanceledException) 58 | { 59 | } 60 | finally 61 | { 62 | _cts.Cancel(snapshot); 63 | } 64 | } 65 | 66 | private void Buffer_Changed(object sender, TextContentChangedEventArgs e) 67 | { 68 | _cts.Cancel(e.Before); 69 | } 70 | 71 | private void Buffer_ChangedOnBackground(object sender, TextContentChangedEventArgs e) 72 | { 73 | if (e.Changes.Count == 0) 74 | return; 75 | 76 | ITextSnapshot snapshot = e.After; 77 | INormalizedTextChangeCollection changes = e.Changes; 78 | CancellationToken cancellationToken = _cts.GetToken(snapshot); 79 | 80 | _analyzedSpans.Synchronize(changes); 81 | if (cancellationToken.IsCancellationRequested) 82 | return; 83 | 84 | try 85 | { 86 | ThreadHelper.JoinableTaskFactory.Run(() => ScanChangesAsync(snapshot, changes, cancellationToken)); 87 | } 88 | catch (OperationCanceledException) 89 | { 90 | } 91 | finally 92 | { 93 | _cts.Cancel(snapshot); 94 | } 95 | } 96 | 97 | private async Task ScanChangesAsync(ITextSnapshot snapshot, INormalizedTextChangeCollection changes, CancellationToken cancellationToken) 98 | { 99 | Document document = snapshot.GetOpenDocumentInCurrentContextWithChanges(); 100 | if (document is null || !document.SupportsSyntaxTree || !document.SupportsSemanticModel) 101 | return; 102 | 103 | SyntaxNode rootNode = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); 104 | SemanticModel semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); 105 | 106 | if (cancellationToken.IsCancellationRequested) 107 | return; 108 | 109 | _analyzedSpans.Rescan(snapshot, changes, rootNode, semanticModel, CancellationToken.None); 110 | TagsChanged?.Invoke(this, new SnapshotSpanEventArgs(new SnapshotSpan(snapshot, new Span(0, snapshot.Length)))); 111 | } 112 | 113 | private async Task ScanDocumentAsync(ITextSnapshot snapshot) 114 | { 115 | Document document = snapshot.GetOpenDocumentInCurrentContextWithChanges(); 116 | if (document is null || !document.SupportsSyntaxTree || !document.SupportsSemanticModel) 117 | return; 118 | 119 | SyntaxNode rootNode = await document.GetSyntaxRootAsync(CancellationToken.None).ConfigureAwait(false); 120 | SemanticModel semanticModel = await document.GetSemanticModelAsync(CancellationToken.None).ConfigureAwait(false); 121 | 122 | _analyzedSpans.RescanDocument(snapshot, rootNode, semanticModel, CancellationToken.None); 123 | TagsChanged?.Invoke(this, new SnapshotSpanEventArgs(new SnapshotSpan(snapshot, new Span(0, snapshot.Length)))); 124 | } 125 | 126 | IEnumerable> ITagger.GetTags(NormalizedSnapshotSpanCollection spans) 127 | { 128 | foreach (SnapshotSpan snapshotSpan in spans) 129 | { 130 | ITextSnapshot snapshot = snapshotSpan.Snapshot; 131 | 132 | foreach (AnalyzedSpan analyzedSpan in _analyzedSpans) 133 | { 134 | if (_buffer.CurrentSnapshot != snapshot) 135 | yield break; 136 | 137 | foreach (SyntaxSpan syntaxSpan in analyzedSpan.GetSyntaxSpansIn(snapshotSpan)) 138 | { 139 | Debug.Assert(snapshotSpan.Start <= syntaxSpan.End && syntaxSpan.Start <= snapshotSpan.End); 140 | 141 | if (syntaxSpan.End > snapshot.Length) 142 | continue; 143 | 144 | yield return _tagSpanFactory.CreateTagSpan(snapshot, in syntaxSpan); 145 | } 146 | } 147 | } 148 | } 149 | 150 | IEnumerable> ITagger.GetTags(NormalizedSnapshotSpanCollection spans) 151 | { 152 | foreach (SnapshotSpan snapshotSpan in spans) 153 | { 154 | ITextSnapshot snapshot = snapshotSpan.Snapshot; 155 | 156 | foreach (AnalyzedSpan analyzedSpan in _analyzedSpans) 157 | { 158 | if (_buffer.CurrentSnapshot != snapshot) 159 | yield break; 160 | 161 | if (!analyzedSpan.TryGetDiagnosticSpanIn(snapshotSpan, out DiagnosticSpan diagnosticSpan)) 162 | continue; 163 | 164 | if (diagnosticSpan.End > snapshot.Length) 165 | continue; 166 | 167 | yield return _tagSpanFactory.CreateTagSpan(snapshot, in diagnosticSpan); 168 | } 169 | } 170 | } 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /GraphQLTools/Tagging/GqlTaggerProvider.cs: -------------------------------------------------------------------------------- 1 | using GraphQLTools.Analysis; 2 | using GraphQLTools.Classification; 3 | using Microsoft.CodeAnalysis; 4 | using Microsoft.Extensions.ObjectPool; 5 | using Microsoft.VisualStudio.LanguageServices; 6 | using Microsoft.VisualStudio.Text; 7 | using Microsoft.VisualStudio.Text.Editor; 8 | using Microsoft.VisualStudio.Text.Tagging; 9 | using Microsoft.VisualStudio.Utilities; 10 | using System; 11 | using System.Collections.Generic; 12 | using System.ComponentModel.Composition; 13 | using SyntaxSpanListPool = Microsoft.Extensions.ObjectPool.ObjectPool; 14 | 15 | namespace GraphQLTools.Tagging 16 | { 17 | [Export(typeof(IViewTaggerProvider))] 18 | [ContentType("csharp")] 19 | [TagType(typeof(IClassificationTag)), TagType(typeof(IErrorTag))] 20 | internal sealed class GqlTaggerProvider : IViewTaggerProvider, IDisposable 21 | { 22 | private static readonly ObjectPoolProvider s_poolProvider = new DefaultObjectPoolProvider(); 23 | 24 | private readonly VisualStudioWorkspace _workspace; 25 | private readonly GqlTagSpanFactory _tagSpanFactory; 26 | private readonly Dictionary _openTaggers; 27 | private readonly SyntaxSpanListPool _spanListPool; 28 | 29 | [ImportingConstructor] 30 | public GqlTaggerProvider(VisualStudioWorkspace workspace, GqlClassificationTypes classificationTypes) 31 | { 32 | _workspace = workspace; 33 | _tagSpanFactory = new GqlTagSpanFactory(classificationTypes); 34 | _openTaggers = new Dictionary(); 35 | _spanListPool = s_poolProvider.Create(SyntaxSpanListPooledObjectPolicy.Instance); 36 | 37 | _workspace.WorkspaceChanged += Workspace_WorkspaceChanged; 38 | } 39 | 40 | public ITagger CreateTagger(ITextView textView, ITextBuffer buffer) 41 | where T : ITag 42 | { 43 | textView.Closed += TextView_Closed; 44 | 45 | return (ITagger)buffer.Properties.GetOrCreateSingletonProperty(() => 46 | { 47 | GqlTagger tagger = new GqlTagger((ITextBuffer2)buffer, _tagSpanFactory, _spanListPool); 48 | _openTaggers.Add(textView, tagger); 49 | tagger.ScanDocument(); 50 | return tagger; 51 | }); 52 | } 53 | 54 | private void TextView_Closed(object sender, EventArgs e) 55 | { 56 | ITextView textView = (ITextView)sender; 57 | 58 | if (_openTaggers.TryGetValue(textView, out GqlTagger tagger)) 59 | { 60 | tagger.Close(); 61 | _openTaggers.Remove(textView); 62 | } 63 | 64 | textView.Closed -= TextView_Closed; 65 | } 66 | 67 | private void Workspace_WorkspaceChanged(object sender, WorkspaceChangeEventArgs e) 68 | { 69 | if (e.Kind != WorkspaceChangeKind.ProjectChanged) 70 | return; 71 | 72 | foreach (GqlTagger tagger in _openTaggers.Values) 73 | { 74 | tagger.ScanDocument(e.ProjectId); 75 | } 76 | } 77 | 78 | public void Dispose() 79 | { 80 | _workspace.WorkspaceChanged -= Workspace_WorkspaceChanged; 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /GraphQLTools/Tagging/TaggerCancellationTokenSource.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.Text; 2 | using System.Collections.Concurrent; 3 | using System.Threading; 4 | 5 | namespace GraphQLTools.Tagging 6 | { 7 | internal readonly struct TaggerCancellationTokenSource 8 | { 9 | private readonly ConcurrentDictionary _sources; 10 | 11 | public TaggerCancellationTokenSource(ConcurrentDictionary cts) 12 | { 13 | _sources = cts; 14 | } 15 | 16 | public void Clear() 17 | { 18 | foreach (CancellationTokenSource cts in _sources.Values) 19 | { 20 | cts.Cancel(); 21 | cts.Dispose(); 22 | } 23 | 24 | _sources.Clear(); 25 | } 26 | 27 | public CancellationToken GetToken(ITextSnapshot snapshot) 28 | { 29 | CancellationTokenSource cts = _sources.GetOrAdd(snapshot, _ => new CancellationTokenSource()); 30 | 31 | return cts.Token; 32 | } 33 | 34 | public void Cancel(ITextSnapshot snapshot) 35 | { 36 | if (!_sources.TryRemove(snapshot, out CancellationTokenSource cts)) 37 | return; 38 | 39 | cts.Cancel(); 40 | cts.Dispose(); 41 | } 42 | 43 | public static TaggerCancellationTokenSource Create() 44 | { 45 | return new TaggerCancellationTokenSource(new ConcurrentDictionary()); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /GraphQLTools/source.extension.vsixmanifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | GraphQLTools 6 | Enables GraphQL syntax highlighting on C# strings with [StringSyntax("GraphQL")]. 7 | LICENSE.txt 8 | Resources\logo.png 9 | Resources\logo.png 10 | graphql syntax highlighting stringsyntaxattribute 11 | true 12 | 13 | 14 | 15 | amd64 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Code Architects 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GraphQLTools 2 | 3 | GraphQLTools is a Visual Studio 2022 extension designed to provide GraphQL support for the `StringSyntaxAttribute` [feature](https://github.com/dotnet/runtime/issues/62505) introduced in .NET 7. 4 | 5 | Currently, there is no native support for GraphQL formatting and syntax highlighting, so that's where GraphQLTools comes in. By decorating your string parameters or members with `[StringSyntax("GraphQL")]` you can incorporate GraphQL syntax highlighting and checking into your C# code. 6 | 7 | ![Syntax highlighting](images/syntax-highlighting.png) 8 | 9 | ## Features and limitations 10 | 11 | GraphQLTools enhances the coding experience by classifying various components of GraphQL documents using distinct syntax highlighting for 12 | 13 | - Punctuators 14 | - Contextual keywords (like `query`, `fragment`, `on`) 15 | - Operation names 16 | - Fragment names 17 | - Variable names 18 | - Directive names 19 | - Type names 20 | - Field names 21 | - Aliased field names 22 | - Argument names 23 | - Object field names 24 | - Strings 25 | - Numbers 26 | - Enums 27 | - Comments 28 | 29 | At this time, only executable definitions (operations and fragments) are supported by the parser, whereas type system definitions and extensions are not. In addition to syntax highlighting, the extension helps identify syntax errors with diagnostic error messages displayed in tooltips upon mouse hover. 30 | 31 | Like in the native feature, regular, verbatim, and raw string literals are supported, while interpolated strings are not. 32 | 33 | It's worth noting that the `StringSyntax` attribute allows flexibility in the casing of the `syntax` parameter within the attribute's constructor. For example, both `[StringSyntax("GraphQL")]` and `[StringSyntax("graphql")]` are valid. 34 | -------------------------------------------------------------------------------- /images/syntax-highlighting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codearchitects/GraphQLTools/77acda10c4c8e9180056729f5b2801430ddbb104/images/syntax-highlighting.png -------------------------------------------------------------------------------- /publishManifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/vsix-publish", 3 | "categories": [ 4 | "build", 5 | "coding" 6 | ], 7 | "identity": { 8 | "internalName": "GraphQLTools" 9 | }, 10 | "overview": "README.md", 11 | "priceCategory": "free", 12 | "publisher": "codearchitects-research", 13 | "private": false, 14 | "qna": true, 15 | "assetFiles": [ 16 | { 17 | "pathOnDisk": ".\\images\\syntax-highlighting.png", 18 | "targetPath": "images/syntax-highlighting.png" 19 | } 20 | ], 21 | "repo": "https://github.com/codearchitects/GraphQLTools" 22 | } 23 | --------------------------------------------------------------------------------