@@ -5,6 +5,23 @@ namespace NativeInvoke.Generator;
55[ Generator ]
66public sealed class NativeImportGenerator : IIncrementalGenerator
77{
8+ private static readonly HashSet < string > ContextualKeywordsThatNeedEscaping ;
9+
10+ static NativeImportGenerator ( )
11+ {
12+ ContextualKeywordsThatNeedEscaping = new HashSet < string > ( StringComparer . Ordinal )
13+ {
14+ // Query keywords
15+ "from" , "where" , "select" , "group" , "into" , "orderby" , "join" , "on" , "equals" , "by" ,
16+ "ascending" , "descending" , "let" ,
17+ // Async keywords
18+ "async" , "await" ,
19+ // Other contextual keywords
20+ "when" , "yield" , "partial" , "file" , "required" , "init" , "set" , "get" , "add" , "remove" ,
21+ "nameof" , "var" , "dynamic"
22+ } ;
23+ }
24+
825 public void Initialize ( IncrementalGeneratorInitializationContext context )
926 {
1027 // Incremental source generator is awesome
@@ -334,8 +351,8 @@ private static string GenerateSource(
334351 {
335352 var m = data . Method ;
336353 var ret = m . ReturnType . ToDisplayString ( ) ;
337- var paramsList = string . Join ( ", " , m . Parameters . Select ( static p => $ "{ p . Type . ToDisplayString ( ) } { p . Name } ") ) ;
338- var argsList = string . Join ( ", " , m . Parameters . Select ( static p => p . Name ) ) ;
354+ var paramsList = string . Join ( ", " , m . Parameters . Select ( static p => $ "{ p . Type . ToDisplayString ( ) } { EscapeParameterName ( p ) } ") ) ;
355+ var argsList = string . Join ( ", " , m . Parameters . Select ( static p => EscapeParameterName ( p ) ) ) ;
339356
340357 if ( data . ShouldInclude )
341358 {
@@ -595,6 +612,51 @@ private static string GetAccessibilityString(ISymbol symbol) =>
595612 Accessibility . ProtectedAndInternal => "private protected " ,
596613 _ => ""
597614 } ;
615+
616+ private static string EscapeParameterName ( IParameterSymbol parameter )
617+ {
618+ // https://github.com/Cheatoid/NativeInvoke/issues/1
619+ // Try to get the raw parameter name from source to preserve @ prefix if it exists
620+ var rawName = GetRawParameterName ( parameter ) ;
621+ if ( ! string . IsNullOrEmpty ( rawName ) )
622+ {
623+ // If we have the raw name with @ prefix, use it directly
624+ return rawName ;
625+ }
626+
627+ // Fallback: Use the symbol name and escape if it's a keyword
628+ var name = parameter . Name ;
629+ return ShouldEscapeIdentifier ( name ) ? $ "@{ name } " : name ;
630+ }
631+
632+ private static string GetRawParameterName ( IParameterSymbol parameter )
633+ {
634+ // Try to get the syntax reference for this parameter
635+ var syntaxReferences = parameter . DeclaringSyntaxReferences ;
636+ if ( syntaxReferences . Length == 0 ) return string . Empty ;
637+
638+ var syntax = syntaxReferences [ 0 ] . GetSyntax ( ) ;
639+ if ( syntax is ParameterSyntax parameterSyntax )
640+ {
641+ // Get the identifier text which preserves the @ prefix
642+ return parameterSyntax . Identifier . Text ;
643+ }
644+
645+ return string . Empty ;
646+ }
647+
648+ private static bool ShouldEscapeIdentifier ( string identifier )
649+ {
650+ // Use Roslyn's SyntaxFacts to check if it's a keyword
651+ if ( SyntaxFacts . GetKeywordKind ( identifier ) != SyntaxKind . None )
652+ {
653+ return true ;
654+ }
655+
656+ // Check for contextual keywords that need escaping in certain contexts
657+ // These are keywords that are only reserved in specific contexts
658+ return ContextualKeywordsThatNeedEscaping . Contains ( identifier ) ;
659+ }
598660}
599661
600662internal static partial class Extensions
0 commit comments