Common.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  1. using System.Linq;
  2. using Microsoft.CodeAnalysis;
  3. using Microsoft.CodeAnalysis.CSharp.Syntax;
  4. using Microsoft.CodeAnalysis.Diagnostics;
  5. namespace Godot.SourceGenerators
  6. {
  7. public static partial class Common
  8. {
  9. private static readonly string _helpLinkFormat = $"{VersionDocsUrl}/tutorials/scripting/c_sharp/diagnostics/{{0}}.html";
  10. public static void ReportNonPartialGodotScriptClass(
  11. GeneratorExecutionContext context,
  12. ClassDeclarationSyntax cds, INamedTypeSymbol symbol
  13. )
  14. {
  15. string message =
  16. "Missing partial modifier on declaration of type '" +
  17. $"{symbol.FullQualifiedNameOmitGlobal()}' that derives from '{GodotClasses.GodotObject}'";
  18. string description = $"{message}. Classes that derive from '{GodotClasses.GodotObject}' " +
  19. "must be declared with the partial modifier.";
  20. context.ReportDiagnostic(Diagnostic.Create(
  21. new DiagnosticDescriptor(id: "GD0001",
  22. title: message,
  23. messageFormat: message,
  24. category: "Usage",
  25. DiagnosticSeverity.Error,
  26. isEnabledByDefault: true,
  27. description,
  28. helpLinkUri: string.Format(_helpLinkFormat, "GD0001")),
  29. cds.GetLocation(),
  30. cds.SyntaxTree.FilePath));
  31. }
  32. public static void ReportNonPartialGodotScriptOuterClass(
  33. GeneratorExecutionContext context,
  34. TypeDeclarationSyntax outerTypeDeclSyntax
  35. )
  36. {
  37. var outerSymbol = context.Compilation
  38. .GetSemanticModel(outerTypeDeclSyntax.SyntaxTree)
  39. .GetDeclaredSymbol(outerTypeDeclSyntax);
  40. string fullQualifiedName = outerSymbol is INamedTypeSymbol namedTypeSymbol ?
  41. namedTypeSymbol.FullQualifiedNameOmitGlobal() :
  42. "type not found";
  43. string message =
  44. $"Missing partial modifier on declaration of type '{fullQualifiedName}', " +
  45. $"which contains nested classes that derive from '{GodotClasses.GodotObject}'";
  46. string description = $"{message}. Classes that derive from '{GodotClasses.GodotObject}' and their " +
  47. "containing types must be declared with the partial modifier.";
  48. context.ReportDiagnostic(Diagnostic.Create(
  49. new DiagnosticDescriptor(id: "GD0002",
  50. title: message,
  51. messageFormat: message,
  52. category: "Usage",
  53. DiagnosticSeverity.Error,
  54. isEnabledByDefault: true,
  55. description,
  56. helpLinkUri: string.Format(_helpLinkFormat, "GD0002")),
  57. outerTypeDeclSyntax.GetLocation(),
  58. outerTypeDeclSyntax.SyntaxTree.FilePath));
  59. }
  60. public static void ReportExportedMemberIsStatic(
  61. GeneratorExecutionContext context,
  62. ISymbol exportedMemberSymbol
  63. )
  64. {
  65. var locations = exportedMemberSymbol.Locations;
  66. var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault();
  67. bool isField = exportedMemberSymbol is IFieldSymbol;
  68. string message = $"Attempted to export static {(isField ? "field" : "property")}: " +
  69. $"'{exportedMemberSymbol.ToDisplayString()}'";
  70. string description = $"{message}. Only instance fields and properties can be exported." +
  71. " Remove the 'static' modifier or the '[Export]' attribute.";
  72. context.ReportDiagnostic(Diagnostic.Create(
  73. new DiagnosticDescriptor(id: "GD0101",
  74. title: message,
  75. messageFormat: message,
  76. category: "Usage",
  77. DiagnosticSeverity.Error,
  78. isEnabledByDefault: true,
  79. description,
  80. helpLinkUri: string.Format(_helpLinkFormat, "GD0101")),
  81. location,
  82. location?.SourceTree?.FilePath));
  83. }
  84. public static void ReportExportedMemberTypeNotSupported(
  85. GeneratorExecutionContext context,
  86. ISymbol exportedMemberSymbol
  87. )
  88. {
  89. var locations = exportedMemberSymbol.Locations;
  90. var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault();
  91. bool isField = exportedMemberSymbol is IFieldSymbol;
  92. string message = $"The type of the exported {(isField ? "field" : "property")} " +
  93. $"is not supported: '{exportedMemberSymbol.ToDisplayString()}'";
  94. string description = $"{message}. Use a supported type or remove the '[Export]' attribute.";
  95. context.ReportDiagnostic(Diagnostic.Create(
  96. new DiagnosticDescriptor(id: "GD0102",
  97. title: message,
  98. messageFormat: message,
  99. category: "Usage",
  100. DiagnosticSeverity.Error,
  101. isEnabledByDefault: true,
  102. description,
  103. helpLinkUri: string.Format(_helpLinkFormat, "GD0102")),
  104. location,
  105. location?.SourceTree?.FilePath));
  106. }
  107. public static void ReportExportedMemberIsReadOnly(
  108. GeneratorExecutionContext context,
  109. ISymbol exportedMemberSymbol
  110. )
  111. {
  112. var locations = exportedMemberSymbol.Locations;
  113. var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault();
  114. bool isField = exportedMemberSymbol is IFieldSymbol;
  115. string message = $"The exported {(isField ? "field" : "property")} " +
  116. $"is read-only: '{exportedMemberSymbol.ToDisplayString()}'";
  117. string description = isField ?
  118. $"{message}. Exported fields cannot be read-only." :
  119. $"{message}. Exported properties must be writable.";
  120. context.ReportDiagnostic(Diagnostic.Create(
  121. new DiagnosticDescriptor(id: "GD0103",
  122. title: message,
  123. messageFormat: message,
  124. category: "Usage",
  125. DiagnosticSeverity.Error,
  126. isEnabledByDefault: true,
  127. description,
  128. helpLinkUri: string.Format(_helpLinkFormat, "GD1003")),
  129. location,
  130. location?.SourceTree?.FilePath));
  131. }
  132. public static void ReportExportedMemberIsWriteOnly(
  133. GeneratorExecutionContext context,
  134. ISymbol exportedMemberSymbol
  135. )
  136. {
  137. var locations = exportedMemberSymbol.Locations;
  138. var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault();
  139. string message = $"The exported property is write-only: '{exportedMemberSymbol.ToDisplayString()}'";
  140. string description = $"{message}. Exported properties must be readable.";
  141. context.ReportDiagnostic(Diagnostic.Create(
  142. new DiagnosticDescriptor(id: "GD0104",
  143. title: message,
  144. messageFormat: message,
  145. category: "Usage",
  146. DiagnosticSeverity.Error,
  147. isEnabledByDefault: true,
  148. description,
  149. helpLinkUri: string.Format(_helpLinkFormat, "GD0104")),
  150. location,
  151. location?.SourceTree?.FilePath));
  152. }
  153. public static void ReportExportedMemberIsIndexer(
  154. GeneratorExecutionContext context,
  155. ISymbol exportedMemberSymbol
  156. )
  157. {
  158. var locations = exportedMemberSymbol.Locations;
  159. var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault();
  160. string message = $"Attempted to export indexer property: " +
  161. $"'{exportedMemberSymbol.ToDisplayString()}'";
  162. string description = $"{message}. Indexer properties can't be exported." +
  163. " Remove the '[Export]' attribute.";
  164. context.ReportDiagnostic(Diagnostic.Create(
  165. new DiagnosticDescriptor(id: "GD0105",
  166. title: message,
  167. messageFormat: message,
  168. category: "Usage",
  169. DiagnosticSeverity.Error,
  170. isEnabledByDefault: true,
  171. description,
  172. helpLinkUri: string.Format(_helpLinkFormat, "GD0105")),
  173. location,
  174. location?.SourceTree?.FilePath));
  175. }
  176. public static void ReportExportedMemberIsExplicitInterfaceImplementation(
  177. GeneratorExecutionContext context,
  178. ISymbol exportedMemberSymbol
  179. )
  180. {
  181. var locations = exportedMemberSymbol.Locations;
  182. var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault();
  183. string message = $"Attempted to export explicit interface property implementation: " +
  184. $"'{exportedMemberSymbol.ToDisplayString()}'";
  185. string description = $"{message}. Explicit interface implementations can't be exported." +
  186. " Remove the '[Export]' attribute.";
  187. context.ReportDiagnostic(Diagnostic.Create(
  188. new DiagnosticDescriptor(id: "GD0106",
  189. title: message,
  190. messageFormat: message,
  191. category: "Usage",
  192. DiagnosticSeverity.Error,
  193. isEnabledByDefault: true,
  194. description,
  195. helpLinkUri: string.Format(_helpLinkFormat, "GD0106")),
  196. location,
  197. location?.SourceTree?.FilePath));
  198. }
  199. public static void ReportSignalDelegateMissingSuffix(
  200. GeneratorExecutionContext context,
  201. INamedTypeSymbol delegateSymbol)
  202. {
  203. var locations = delegateSymbol.Locations;
  204. var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault();
  205. string message = "The name of the delegate must end with 'EventHandler': " +
  206. delegateSymbol.ToDisplayString() +
  207. $". Did you mean '{delegateSymbol.Name}EventHandler'?";
  208. string description = $"{message}. Rename the delegate accordingly or remove the '[Signal]' attribute.";
  209. context.ReportDiagnostic(Diagnostic.Create(
  210. new DiagnosticDescriptor(id: "GD0201",
  211. title: message,
  212. messageFormat: message,
  213. category: "Usage",
  214. DiagnosticSeverity.Error,
  215. isEnabledByDefault: true,
  216. description,
  217. helpLinkUri: string.Format(_helpLinkFormat, "GD0201")),
  218. location,
  219. location?.SourceTree?.FilePath));
  220. }
  221. public static void ReportSignalParameterTypeNotSupported(
  222. GeneratorExecutionContext context,
  223. IParameterSymbol parameterSymbol)
  224. {
  225. var locations = parameterSymbol.Locations;
  226. var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault();
  227. string message = "The parameter of the delegate signature of the signal " +
  228. $"is not supported: '{parameterSymbol.ToDisplayString()}'";
  229. string description = $"{message}. Use supported types only or remove the '[Signal]' attribute.";
  230. context.ReportDiagnostic(Diagnostic.Create(
  231. new DiagnosticDescriptor(id: "GD0202",
  232. title: message,
  233. messageFormat: message,
  234. category: "Usage",
  235. DiagnosticSeverity.Error,
  236. isEnabledByDefault: true,
  237. description,
  238. helpLinkUri: string.Format(_helpLinkFormat, "GD0202")),
  239. location,
  240. location?.SourceTree?.FilePath));
  241. }
  242. public static void ReportSignalDelegateSignatureMustReturnVoid(
  243. GeneratorExecutionContext context,
  244. INamedTypeSymbol delegateSymbol)
  245. {
  246. var locations = delegateSymbol.Locations;
  247. var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault();
  248. string message = "The delegate signature of the signal " +
  249. $"must return void: '{delegateSymbol.ToDisplayString()}'";
  250. string description = $"{message}. Return void or remove the '[Signal]' attribute.";
  251. context.ReportDiagnostic(Diagnostic.Create(
  252. new DiagnosticDescriptor(id: "GD0203",
  253. title: message,
  254. messageFormat: message,
  255. category: "Usage",
  256. DiagnosticSeverity.Error,
  257. isEnabledByDefault: true,
  258. description,
  259. helpLinkUri: string.Format(_helpLinkFormat, "GD0203")),
  260. location,
  261. location?.SourceTree?.FilePath));
  262. }
  263. public static readonly DiagnosticDescriptor GenericTypeArgumentMustBeVariantRule =
  264. new DiagnosticDescriptor(id: "GD0301",
  265. title: "The generic type argument must be a Variant compatible type",
  266. messageFormat: "The generic type argument must be a Variant compatible type: {0}",
  267. category: "Usage",
  268. DiagnosticSeverity.Error,
  269. isEnabledByDefault: true,
  270. "The generic type argument must be a Variant compatible type. Use a Variant compatible type as the generic type argument.",
  271. helpLinkUri: string.Format(_helpLinkFormat, "GD0301"));
  272. public static void ReportGenericTypeArgumentMustBeVariant(
  273. SyntaxNodeAnalysisContext context,
  274. SyntaxNode typeArgumentSyntax,
  275. ISymbol typeArgumentSymbol)
  276. {
  277. string message = "The generic type argument " +
  278. $"must be a Variant compatible type: '{typeArgumentSymbol.ToDisplayString()}'";
  279. string description = $"{message}. Use a Variant compatible type as the generic type argument.";
  280. context.ReportDiagnostic(Diagnostic.Create(
  281. new DiagnosticDescriptor(id: "GD0301",
  282. title: message,
  283. messageFormat: message,
  284. category: "Usage",
  285. DiagnosticSeverity.Error,
  286. isEnabledByDefault: true,
  287. description,
  288. helpLinkUri: string.Format(_helpLinkFormat, "GD0301")),
  289. typeArgumentSyntax.GetLocation(),
  290. typeArgumentSyntax.SyntaxTree.FilePath));
  291. }
  292. public static readonly DiagnosticDescriptor GenericTypeParameterMustBeVariantAnnotatedRule =
  293. new DiagnosticDescriptor(id: "GD0302",
  294. title: "The generic type parameter must be annotated with the MustBeVariant attribute",
  295. messageFormat: "The generic type argument must be a Variant type: {0}",
  296. category: "Usage",
  297. DiagnosticSeverity.Error,
  298. isEnabledByDefault: true,
  299. "The generic type argument must be a Variant type. Use a Variant type as the generic type argument.",
  300. helpLinkUri: string.Format(_helpLinkFormat, "GD0302"));
  301. public static void ReportGenericTypeParameterMustBeVariantAnnotated(
  302. SyntaxNodeAnalysisContext context,
  303. SyntaxNode typeArgumentSyntax,
  304. ISymbol typeArgumentSymbol)
  305. {
  306. string message = "The generic type parameter must be annotated with the MustBeVariant attribute";
  307. string description = $"{message}. Add the MustBeVariant attribute to the generic type parameter.";
  308. context.ReportDiagnostic(Diagnostic.Create(
  309. new DiagnosticDescriptor(id: "GD0302",
  310. title: message,
  311. messageFormat: message,
  312. category: "Usage",
  313. DiagnosticSeverity.Error,
  314. isEnabledByDefault: true,
  315. description,
  316. helpLinkUri: string.Format(_helpLinkFormat, "GD0302")),
  317. typeArgumentSyntax.GetLocation(),
  318. typeArgumentSyntax.SyntaxTree.FilePath));
  319. }
  320. public static readonly DiagnosticDescriptor TypeArgumentParentSymbolUnhandledRule =
  321. new DiagnosticDescriptor(id: "GD0303",
  322. title: "The generic type parameter must be annotated with the MustBeVariant attribute",
  323. messageFormat: "The generic type argument must be a Variant type: {0}",
  324. category: "Usage",
  325. DiagnosticSeverity.Error,
  326. isEnabledByDefault: true,
  327. "The generic type argument must be a Variant type. Use a Variant type as the generic type argument.",
  328. helpLinkUri: string.Format(_helpLinkFormat, "GD0303"));
  329. public static void ReportTypeArgumentParentSymbolUnhandled(
  330. SyntaxNodeAnalysisContext context,
  331. SyntaxNode typeArgumentSyntax,
  332. ISymbol parentSymbol)
  333. {
  334. string message = $"Symbol '{parentSymbol.ToDisplayString()}' parent of a type argument " +
  335. "that must be Variant compatible was not handled.";
  336. string description = $"{message}. Handle type arguments that are children of the unhandled symbol type.";
  337. context.ReportDiagnostic(Diagnostic.Create(
  338. new DiagnosticDescriptor(id: "GD0303",
  339. title: message,
  340. messageFormat: message,
  341. category: "Usage",
  342. DiagnosticSeverity.Error,
  343. isEnabledByDefault: true,
  344. description,
  345. helpLinkUri: string.Format(_helpLinkFormat, "GD0303")),
  346. typeArgumentSyntax.GetLocation(),
  347. typeArgumentSyntax.SyntaxTree.FilePath));
  348. }
  349. public static readonly DiagnosticDescriptor GlobalClassMustDeriveFromGodotObjectRule =
  350. new DiagnosticDescriptor(id: "GD0401",
  351. title: "The class must derive from GodotObject or a derived class",
  352. messageFormat: "The class '{0}' must derive from GodotObject or a derived class",
  353. category: "Usage",
  354. DiagnosticSeverity.Error,
  355. isEnabledByDefault: true,
  356. "The class must derive from GodotObject or a derived class. Change the base class or remove the '[GlobalClass]' attribute.",
  357. helpLinkUri: string.Format(_helpLinkFormat, "GD0401"));
  358. public static void ReportGlobalClassMustDeriveFromGodotObject(
  359. SyntaxNodeAnalysisContext context,
  360. SyntaxNode classSyntax,
  361. ISymbol typeSymbol)
  362. {
  363. string message = $"The class '{typeSymbol.ToDisplayString()}' must derive from GodotObject or a derived class";
  364. string description = $"{message}. Change the base class or remove the '[GlobalClass]' attribute.";
  365. context.ReportDiagnostic(Diagnostic.Create(
  366. new DiagnosticDescriptor(id: "GD0401",
  367. title: message,
  368. messageFormat: message,
  369. category: "Usage",
  370. DiagnosticSeverity.Error,
  371. isEnabledByDefault: true,
  372. description,
  373. helpLinkUri: string.Format(_helpLinkFormat, "GD0401")),
  374. classSyntax.GetLocation(),
  375. classSyntax.SyntaxTree.FilePath));
  376. }
  377. public static readonly DiagnosticDescriptor GlobalClassMustNotBeGenericRule =
  378. new DiagnosticDescriptor(id: "GD0402",
  379. title: "The class must not contain generic arguments",
  380. messageFormat: "The class '{0}' must not contain generic arguments",
  381. category: "Usage",
  382. DiagnosticSeverity.Error,
  383. isEnabledByDefault: true,
  384. "The class must be a non-generic type. Remove the generic arguments or the '[GlobalClass]' attribute.",
  385. helpLinkUri: string.Format(_helpLinkFormat, "GD0401"));
  386. public static void ReportGlobalClassMustNotBeGeneric(
  387. SyntaxNodeAnalysisContext context,
  388. SyntaxNode classSyntax,
  389. ISymbol typeSymbol)
  390. {
  391. string message = $"The class '{typeSymbol.ToDisplayString()}' must not contain generic arguments";
  392. string description = $"{message}. Remove the generic arguments or the '[GlobalClass]' attribute.";
  393. context.ReportDiagnostic(Diagnostic.Create(
  394. new DiagnosticDescriptor(id: "GD0402",
  395. title: message,
  396. messageFormat: message,
  397. category: "Usage",
  398. DiagnosticSeverity.Error,
  399. isEnabledByDefault: true,
  400. description,
  401. helpLinkUri: string.Format(_helpLinkFormat, "GD0402")),
  402. classSyntax.GetLocation(),
  403. classSyntax.SyntaxTree.FilePath));
  404. }
  405. }
  406. }