├── .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 |
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 | 
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 |
--------------------------------------------------------------------------------