Common.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496
  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 ReportOnlyNodesShouldExportNodes(
  200. GeneratorExecutionContext context,
  201. ISymbol exportedMemberSymbol
  202. )
  203. {
  204. var locations = exportedMemberSymbol.Locations;
  205. var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault();
  206. bool isField = exportedMemberSymbol is IFieldSymbol;
  207. string message = $"Types not derived from Node should not export Node {(isField ? "fields" : "properties")}";
  208. string description = $"{message}. Node export is only supported in Node-derived classes.";
  209. context.ReportDiagnostic(Diagnostic.Create(
  210. new DiagnosticDescriptor(id: "GD0107",
  211. title: message,
  212. messageFormat: message,
  213. category: "Usage",
  214. DiagnosticSeverity.Error,
  215. isEnabledByDefault: true,
  216. description),
  217. location,
  218. location?.SourceTree?.FilePath));
  219. }
  220. public static void ReportSignalDelegateMissingSuffix(
  221. GeneratorExecutionContext context,
  222. INamedTypeSymbol delegateSymbol)
  223. {
  224. var locations = delegateSymbol.Locations;
  225. var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault();
  226. string message = "The name of the delegate must end with 'EventHandler': " +
  227. delegateSymbol.ToDisplayString() +
  228. $". Did you mean '{delegateSymbol.Name}EventHandler'?";
  229. string description = $"{message}. Rename the delegate accordingly or remove the '[Signal]' attribute.";
  230. context.ReportDiagnostic(Diagnostic.Create(
  231. new DiagnosticDescriptor(id: "GD0201",
  232. title: message,
  233. messageFormat: message,
  234. category: "Usage",
  235. DiagnosticSeverity.Error,
  236. isEnabledByDefault: true,
  237. description,
  238. helpLinkUri: string.Format(_helpLinkFormat, "GD0201")),
  239. location,
  240. location?.SourceTree?.FilePath));
  241. }
  242. public static void ReportSignalParameterTypeNotSupported(
  243. GeneratorExecutionContext context,
  244. IParameterSymbol parameterSymbol)
  245. {
  246. var locations = parameterSymbol.Locations;
  247. var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault();
  248. string message = "The parameter of the delegate signature of the signal " +
  249. $"is not supported: '{parameterSymbol.ToDisplayString()}'";
  250. string description = $"{message}. Use supported types only or remove the '[Signal]' attribute.";
  251. context.ReportDiagnostic(Diagnostic.Create(
  252. new DiagnosticDescriptor(id: "GD0202",
  253. title: message,
  254. messageFormat: message,
  255. category: "Usage",
  256. DiagnosticSeverity.Error,
  257. isEnabledByDefault: true,
  258. description,
  259. helpLinkUri: string.Format(_helpLinkFormat, "GD0202")),
  260. location,
  261. location?.SourceTree?.FilePath));
  262. }
  263. public static void ReportSignalDelegateSignatureMustReturnVoid(
  264. GeneratorExecutionContext context,
  265. INamedTypeSymbol delegateSymbol)
  266. {
  267. var locations = delegateSymbol.Locations;
  268. var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault();
  269. string message = "The delegate signature of the signal " +
  270. $"must return void: '{delegateSymbol.ToDisplayString()}'";
  271. string description = $"{message}. Return void or remove the '[Signal]' attribute.";
  272. context.ReportDiagnostic(Diagnostic.Create(
  273. new DiagnosticDescriptor(id: "GD0203",
  274. title: message,
  275. messageFormat: message,
  276. category: "Usage",
  277. DiagnosticSeverity.Error,
  278. isEnabledByDefault: true,
  279. description,
  280. helpLinkUri: string.Format(_helpLinkFormat, "GD0203")),
  281. location,
  282. location?.SourceTree?.FilePath));
  283. }
  284. public static readonly DiagnosticDescriptor GenericTypeArgumentMustBeVariantRule =
  285. new DiagnosticDescriptor(id: "GD0301",
  286. title: "The generic type argument must be a Variant compatible type",
  287. messageFormat: "The generic type argument must be a Variant compatible type: {0}",
  288. category: "Usage",
  289. DiagnosticSeverity.Error,
  290. isEnabledByDefault: true,
  291. "The generic type argument must be a Variant compatible type. Use a Variant compatible type as the generic type argument.",
  292. helpLinkUri: string.Format(_helpLinkFormat, "GD0301"));
  293. public static void ReportGenericTypeArgumentMustBeVariant(
  294. SyntaxNodeAnalysisContext context,
  295. SyntaxNode typeArgumentSyntax,
  296. ISymbol typeArgumentSymbol)
  297. {
  298. string message = "The generic type argument " +
  299. $"must be a Variant compatible type: '{typeArgumentSymbol.ToDisplayString()}'";
  300. string description = $"{message}. Use a Variant compatible type as the generic type argument.";
  301. context.ReportDiagnostic(Diagnostic.Create(
  302. new DiagnosticDescriptor(id: "GD0301",
  303. title: message,
  304. messageFormat: message,
  305. category: "Usage",
  306. DiagnosticSeverity.Error,
  307. isEnabledByDefault: true,
  308. description,
  309. helpLinkUri: string.Format(_helpLinkFormat, "GD0301")),
  310. typeArgumentSyntax.GetLocation(),
  311. typeArgumentSyntax.SyntaxTree.FilePath));
  312. }
  313. public static readonly DiagnosticDescriptor GenericTypeParameterMustBeVariantAnnotatedRule =
  314. new DiagnosticDescriptor(id: "GD0302",
  315. title: "The generic type parameter must be annotated with the MustBeVariant attribute",
  316. messageFormat: "The generic type argument must be a Variant type: {0}",
  317. category: "Usage",
  318. DiagnosticSeverity.Error,
  319. isEnabledByDefault: true,
  320. "The generic type argument must be a Variant type. Use a Variant type as the generic type argument.",
  321. helpLinkUri: string.Format(_helpLinkFormat, "GD0302"));
  322. public static void ReportGenericTypeParameterMustBeVariantAnnotated(
  323. SyntaxNodeAnalysisContext context,
  324. SyntaxNode typeArgumentSyntax,
  325. ISymbol typeArgumentSymbol)
  326. {
  327. string message = "The generic type parameter must be annotated with the MustBeVariant attribute";
  328. string description = $"{message}. Add the MustBeVariant attribute to the generic type parameter.";
  329. context.ReportDiagnostic(Diagnostic.Create(
  330. new DiagnosticDescriptor(id: "GD0302",
  331. title: message,
  332. messageFormat: message,
  333. category: "Usage",
  334. DiagnosticSeverity.Error,
  335. isEnabledByDefault: true,
  336. description,
  337. helpLinkUri: string.Format(_helpLinkFormat, "GD0302")),
  338. typeArgumentSyntax.GetLocation(),
  339. typeArgumentSyntax.SyntaxTree.FilePath));
  340. }
  341. public static readonly DiagnosticDescriptor TypeArgumentParentSymbolUnhandledRule =
  342. new DiagnosticDescriptor(id: "GD0303",
  343. title: "The generic type parameter must be annotated with the MustBeVariant attribute",
  344. messageFormat: "The generic type argument must be a Variant type: {0}",
  345. category: "Usage",
  346. DiagnosticSeverity.Error,
  347. isEnabledByDefault: true,
  348. "The generic type argument must be a Variant type. Use a Variant type as the generic type argument.",
  349. helpLinkUri: string.Format(_helpLinkFormat, "GD0303"));
  350. public static void ReportTypeArgumentParentSymbolUnhandled(
  351. SyntaxNodeAnalysisContext context,
  352. SyntaxNode typeArgumentSyntax,
  353. ISymbol parentSymbol)
  354. {
  355. string message = $"Symbol '{parentSymbol.ToDisplayString()}' parent of a type argument " +
  356. "that must be Variant compatible was not handled.";
  357. string description = $"{message}. Handle type arguments that are children of the unhandled symbol type.";
  358. context.ReportDiagnostic(Diagnostic.Create(
  359. new DiagnosticDescriptor(id: "GD0303",
  360. title: message,
  361. messageFormat: message,
  362. category: "Usage",
  363. DiagnosticSeverity.Error,
  364. isEnabledByDefault: true,
  365. description,
  366. helpLinkUri: string.Format(_helpLinkFormat, "GD0303")),
  367. typeArgumentSyntax.GetLocation(),
  368. typeArgumentSyntax.SyntaxTree.FilePath));
  369. }
  370. public static readonly DiagnosticDescriptor GlobalClassMustDeriveFromGodotObjectRule =
  371. new DiagnosticDescriptor(id: "GD0401",
  372. title: "The class must derive from GodotObject or a derived class",
  373. messageFormat: "The class '{0}' must derive from GodotObject or a derived class",
  374. category: "Usage",
  375. DiagnosticSeverity.Error,
  376. isEnabledByDefault: true,
  377. "The class must derive from GodotObject or a derived class. Change the base class or remove the '[GlobalClass]' attribute.",
  378. helpLinkUri: string.Format(_helpLinkFormat, "GD0401"));
  379. public static void ReportGlobalClassMustDeriveFromGodotObject(
  380. SyntaxNodeAnalysisContext context,
  381. SyntaxNode classSyntax,
  382. ISymbol typeSymbol)
  383. {
  384. string message = $"The class '{typeSymbol.ToDisplayString()}' must derive from GodotObject or a derived class";
  385. string description = $"{message}. Change the base class or remove the '[GlobalClass]' attribute.";
  386. context.ReportDiagnostic(Diagnostic.Create(
  387. new DiagnosticDescriptor(id: "GD0401",
  388. title: message,
  389. messageFormat: message,
  390. category: "Usage",
  391. DiagnosticSeverity.Error,
  392. isEnabledByDefault: true,
  393. description,
  394. helpLinkUri: string.Format(_helpLinkFormat, "GD0401")),
  395. classSyntax.GetLocation(),
  396. classSyntax.SyntaxTree.FilePath));
  397. }
  398. public static readonly DiagnosticDescriptor GlobalClassMustNotBeGenericRule =
  399. new DiagnosticDescriptor(id: "GD0402",
  400. title: "The class must not contain generic arguments",
  401. messageFormat: "The class '{0}' must not contain generic arguments",
  402. category: "Usage",
  403. DiagnosticSeverity.Error,
  404. isEnabledByDefault: true,
  405. "The class must be a non-generic type. Remove the generic arguments or the '[GlobalClass]' attribute.",
  406. helpLinkUri: string.Format(_helpLinkFormat, "GD0401"));
  407. public static void ReportGlobalClassMustNotBeGeneric(
  408. SyntaxNodeAnalysisContext context,
  409. SyntaxNode classSyntax,
  410. ISymbol typeSymbol)
  411. {
  412. string message = $"The class '{typeSymbol.ToDisplayString()}' must not contain generic arguments";
  413. string description = $"{message}. Remove the generic arguments or the '[GlobalClass]' attribute.";
  414. context.ReportDiagnostic(Diagnostic.Create(
  415. new DiagnosticDescriptor(id: "GD0402",
  416. title: message,
  417. messageFormat: message,
  418. category: "Usage",
  419. DiagnosticSeverity.Error,
  420. isEnabledByDefault: true,
  421. description,
  422. helpLinkUri: string.Format(_helpLinkFormat, "GD0402")),
  423. classSyntax.GetLocation(),
  424. classSyntax.SyntaxTree.FilePath));
  425. }
  426. }
  427. }