mxrb 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (237) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.de-DE.md +108 -0
  4. data/README.md +172 -0
  5. data/README.pt-BR.md +109 -0
  6. data/bin/mxrb +1613 -0
  7. data/docs/README.md +10 -0
  8. data/docs/de-DE/README.md +24 -0
  9. data/docs/de-DE/architectural-patterns.md +26 -0
  10. data/docs/de-DE/architectural-standard.md +48 -0
  11. data/docs/de-DE/architecture.md +76 -0
  12. data/docs/de-DE/compiler.md +162 -0
  13. data/docs/de-DE/conventions.md +19 -0
  14. data/docs/de-DE/design-system.md +52 -0
  15. data/docs/de-DE/entity-dsl.md +53 -0
  16. data/docs/de-DE/native-runtime-quality-report.md +74 -0
  17. data/docs/de-DE/oql-sql.md +211 -0
  18. data/docs/de-DE/platform-api-integration.md +36 -0
  19. data/docs/de-DE/platform-operations.md +243 -0
  20. data/docs/de-DE/project-structure.md +32 -0
  21. data/docs/de-DE/ruby-first-roadmap.md +116 -0
  22. data/docs/de-DE/scaffolds.md +59 -0
  23. data/docs/de-DE/semantic-refactoring.md +56 -0
  24. data/docs/de-DE/team-server.md +33 -0
  25. data/docs/de-DE/validation-matrix.md +64 -0
  26. data/docs/de-DE/vetclinic-acceptance.md +24 -0
  27. data/docs/de-DE/writing.md +138 -0
  28. data/docs/en-US/README.md +24 -0
  29. data/docs/en-US/architectural-patterns.md +151 -0
  30. data/docs/en-US/architectural-standard.md +89 -0
  31. data/docs/en-US/architecture.md +170 -0
  32. data/docs/en-US/compiler.md +157 -0
  33. data/docs/en-US/conventions.md +82 -0
  34. data/docs/en-US/design-system.md +163 -0
  35. data/docs/en-US/entity-dsl.md +53 -0
  36. data/docs/en-US/native-runtime-quality-report.md +73 -0
  37. data/docs/en-US/oql-sql.md +201 -0
  38. data/docs/en-US/platform-api-integration.md +36 -0
  39. data/docs/en-US/platform-operations.md +244 -0
  40. data/docs/en-US/project-structure.md +49 -0
  41. data/docs/en-US/ruby-first-roadmap.md +186 -0
  42. data/docs/en-US/scaffolds.md +57 -0
  43. data/docs/en-US/semantic-refactoring.md +198 -0
  44. data/docs/en-US/team-server.md +32 -0
  45. data/docs/en-US/validation-matrix.md +257 -0
  46. data/docs/en-US/vetclinic-acceptance.md +23 -0
  47. data/docs/en-US/writing.md +655 -0
  48. data/docs/pt-BR/README.md +24 -0
  49. data/docs/pt-BR/architectural-patterns.md +25 -0
  50. data/docs/pt-BR/architectural-standard.md +533 -0
  51. data/docs/pt-BR/architecture.md +300 -0
  52. data/docs/pt-BR/compiler.md +162 -0
  53. data/docs/pt-BR/conventions.md +19 -0
  54. data/docs/pt-BR/design-system.md +65 -0
  55. data/docs/pt-BR/entity-dsl.md +116 -0
  56. data/docs/pt-BR/native-runtime-quality-report.md +73 -0
  57. data/docs/pt-BR/oql-sql.md +208 -0
  58. data/docs/pt-BR/platform-api-integration.md +36 -0
  59. data/docs/pt-BR/platform-operations.md +260 -0
  60. data/docs/pt-BR/project-structure.md +32 -0
  61. data/docs/pt-BR/ruby-first-roadmap.md +239 -0
  62. data/docs/pt-BR/scaffolds.md +104 -0
  63. data/docs/pt-BR/semantic-refactoring.md +55 -0
  64. data/docs/pt-BR/team-server.md +59 -0
  65. data/docs/pt-BR/validation-matrix.md +86 -0
  66. data/docs/pt-BR/vetclinic-acceptance.md +50 -0
  67. data/docs/pt-BR/writing.md +177 -0
  68. data/examples/sudoku_evaluation.rb +14 -0
  69. data/examples/sudoku_functional_test.rb +28 -0
  70. data/lib/mxrb/architecture/graph.rb +124 -0
  71. data/lib/mxrb/architecture/validator.rb +98 -0
  72. data/lib/mxrb/benchmark.rb +33 -0
  73. data/lib/mxrb/compare.rb +390 -0
  74. data/lib/mxrb/compiler/adapter.rb +96 -0
  75. data/lib/mxrb/compiler/artifact_document_compiler.rb +102 -0
  76. data/lib/mxrb/compiler/artifact_materializer.rb +53 -0
  77. data/lib/mxrb/compiler/client_model_materializer.rb +42 -0
  78. data/lib/mxrb/compiler/code_action_document_compiler.rb +64 -0
  79. data/lib/mxrb/compiler/code_action_materializer.rb +35 -0
  80. data/lib/mxrb/compiler/code_action_type_compiler.rb +32 -0
  81. data/lib/mxrb/compiler/combo_box_bundle_compiler.rb +314 -0
  82. data/lib/mxrb/compiler/compatibility_analyzer.rb +169 -0
  83. data/lib/mxrb/compiler/constants_materializer.rb +111 -0
  84. data/lib/mxrb/compiler/data_grid_bundle_compiler.rb +290 -0
  85. data/lib/mxrb/compiler/database_connector_action_compiler.rb +280 -0
  86. data/lib/mxrb/compiler/deployment_asset_copier.rb +60 -0
  87. data/lib/mxrb/compiler/deployment_bootstrapper.rb +208 -0
  88. data/lib/mxrb/compiler/deployment_materializer.rb +43 -0
  89. data/lib/mxrb/compiler/deployment_metadata.rb +64 -0
  90. data/lib/mxrb/compiler/domain_document_compiler.rb +153 -0
  91. data/lib/mxrb/compiler/domain_model_materializer.rb +35 -0
  92. data/lib/mxrb/compiler/domain_security_compiler.rb +82 -0
  93. data/lib/mxrb/compiler/gallery_bundle_compiler.rb +190 -0
  94. data/lib/mxrb/compiler/generic_widget_bundle_compiler.rb +281 -0
  95. data/lib/mxrb/compiler/image_bundle_compiler.rb +216 -0
  96. data/lib/mxrb/compiler/java_proxy_generator.rb +509 -0
  97. data/lib/mxrb/compiler/legacy_data_grid_compiler.rb +267 -0
  98. data/lib/mxrb/compiler/legacy_page_builder.rb +287 -0
  99. data/lib/mxrb/compiler/mda.rb +99 -0
  100. data/lib/mxrb/compiler/microflow_document_compiler.rb +85 -0
  101. data/lib/mxrb/compiler/microflow_materializer.rb +48 -0
  102. data/lib/mxrb/compiler/microflow_node_compiler.rb +250 -0
  103. data/lib/mxrb/compiler/model_package.rb +116 -0
  104. data/lib/mxrb/compiler/model_values.rb +67 -0
  105. data/lib/mxrb/compiler/nanoflow_program_compiler.rb +483 -0
  106. data/lib/mxrb/compiler/navigation_document_compiler.rb +93 -0
  107. data/lib/mxrb/compiler/packager.rb +100 -0
  108. data/lib/mxrb/compiler/page_bundle_builder.rb +56 -0
  109. data/lib/mxrb/compiler/page_bundle_compiler.rb +2004 -0
  110. data/lib/mxrb/compiler/page_document_compiler.rb +77 -0
  111. data/lib/mxrb/compiler/portable_packager.rb +356 -0
  112. data/lib/mxrb/compiler/project_jar_archive.rb +84 -0
  113. data/lib/mxrb/compiler/project_jar_builder.rb +121 -0
  114. data/lib/mxrb/compiler/project_materializer.rb +139 -0
  115. data/lib/mxrb/compiler/project_model_orderer.rb +52 -0
  116. data/lib/mxrb/compiler/runtime_data_types.rb +31 -0
  117. data/lib/mxrb/compiler/runtime_model_schema.rb +90 -0
  118. data/lib/mxrb/compiler/schemas/runtime-10.24.0.73019.json +1052 -0
  119. data/lib/mxrb/compiler/schemas/runtime-11.json +1215 -0
  120. data/lib/mxrb/compiler/schemas/runtime-6.10.8.json +1148 -0
  121. data/lib/mxrb/compiler/schemas/runtime-7.17.0.json +1144 -0
  122. data/lib/mxrb/compiler/schemas/runtime-7.5.0.json +1215 -0
  123. data/lib/mxrb/compiler/schemas/runtime-9.6.1.29396.json +868 -0
  124. data/lib/mxrb/compiler/schemas/system-model-10.24.0.73019.b64 +1135 -0
  125. data/lib/mxrb/compiler/schemas/system-model-11.12.1.b64 +815 -0
  126. data/lib/mxrb/compiler/schemas/system-model-6.10.8.b64 +497 -0
  127. data/lib/mxrb/compiler/schemas/system-model-7.17.0.b64 +519 -0
  128. data/lib/mxrb/compiler/schemas/system-model-7.5.0.b64 +504 -0
  129. data/lib/mxrb/compiler/schemas/system-model-9.6.1.29396.b64 +816 -0
  130. data/lib/mxrb/compiler/security_materializer.rb +104 -0
  131. data/lib/mxrb/compiler/settings_document_compiler.rb +45 -0
  132. data/lib/mxrb/compiler/settings_materializer.rb +25 -0
  133. data/lib/mxrb/compiler/source_model.rb +128 -0
  134. data/lib/mxrb/compiler/system_model_seed.rb +88 -0
  135. data/lib/mxrb/compiler/system_queue_materializer.rb +54 -0
  136. data/lib/mxrb/compiler/system_text_materializer.rb +49 -0
  137. data/lib/mxrb/compiler/translation_materializer.rb +95 -0
  138. data/lib/mxrb/compiler/web_bundle_builder.rb +155 -0
  139. data/lib/mxrb/compiler/web_list_data_source.rb +139 -0
  140. data/lib/mxrb/compiler/web_operation_compiler.rb +614 -0
  141. data/lib/mxrb/compiler/web_shell_materializer.rb +194 -0
  142. data/lib/mxrb/compiler/widget_package_extractor.rb +63 -0
  143. data/lib/mxrb/doctor.rb +116 -0
  144. data/lib/mxrb/dsl/builder.rb +1636 -0
  145. data/lib/mxrb/errors.rb +18 -0
  146. data/lib/mxrb/evaluation.rb +131 -0
  147. data/lib/mxrb/exporter.rb +1716 -0
  148. data/lib/mxrb/frontend/migrator.rb +775 -0
  149. data/lib/mxrb/functional.rb +307 -0
  150. data/lib/mxrb/github/annotator.rb +161 -0
  151. data/lib/mxrb/initializer.rb +257 -0
  152. data/lib/mxrb/integrity/validator.rb +136 -0
  153. data/lib/mxrb/io/bson_codec.rb +145 -0
  154. data/lib/mxrb/io/mpr_file.rb +558 -0
  155. data/lib/mxrb/io/mxunit_codec.rb +42 -0
  156. data/lib/mxrb/marketplace.rb +380 -0
  157. data/lib/mxrb/model/association.rb +98 -0
  158. data/lib/mxrb/model/attribute.rb +100 -0
  159. data/lib/mxrb/model/connector.rb +17 -0
  160. data/lib/mxrb/model/design_materializer.rb +133 -0
  161. data/lib/mxrb/model/design_migration.rb +126 -0
  162. data/lib/mxrb/model/design_system.rb +111 -0
  163. data/lib/mxrb/model/domain_model.rb +51 -0
  164. data/lib/mxrb/model/entity.rb +180 -0
  165. data/lib/mxrb/model/menu.rb +38 -0
  166. data/lib/mxrb/model/microflow.rb +100 -0
  167. data/lib/mxrb/model/module.rb +142 -0
  168. data/lib/mxrb/model/navigation.rb +127 -0
  169. data/lib/mxrb/model/page.rb +410 -0
  170. data/lib/mxrb/model/project.rb +187 -0
  171. data/lib/mxrb/model/unit.rb +48 -0
  172. data/lib/mxrb/module_initializer.rb +75 -0
  173. data/lib/mxrb/official_marketplace/content_api.rb +292 -0
  174. data/lib/mxrb/official_marketplace/dependency_resolver.rb +329 -0
  175. data/lib/mxrb/official_marketplace/lifecycle.rb +367 -0
  176. data/lib/mxrb/official_marketplace/module_package_importer.rb +318 -0
  177. data/lib/mxrb/official_marketplace/widget_package_installer.rb +414 -0
  178. data/lib/mxrb/official_marketplace.rb +720 -0
  179. data/lib/mxrb/oql/analyzer.rb +155 -0
  180. data/lib/mxrb/oql/index_advisor.rb +85 -0
  181. data/lib/mxrb/oql/plan_analyzer.rb +185 -0
  182. data/lib/mxrb/oql/server.rb +132 -0
  183. data/lib/mxrb/oql/sql_server_plan_analyzer.rb +190 -0
  184. data/lib/mxrb/oql/sql_server_workload_analyzer.rb +80 -0
  185. data/lib/mxrb/oql/workload_analyzer.rb +175 -0
  186. data/lib/mxrb/oql/workload_baseline.rb +60 -0
  187. data/lib/mxrb/oql.rb +424 -0
  188. data/lib/mxrb/project_lifecycle.rb +76 -0
  189. data/lib/mxrb/protocols/plan.rb +86 -0
  190. data/lib/mxrb/protocols.rb +123 -0
  191. data/lib/mxrb/runtime/database_workspace.rb +677 -0
  192. data/lib/mxrb/runtime/docker_executor.rb +41 -0
  193. data/lib/mxrb/runtime/docker_workspace.rb +29 -0
  194. data/lib/mxrb/runtime/executor.rb +169 -0
  195. data/lib/mxrb/runtime/java_locator.rb +26 -0
  196. data/lib/mxrb/runtime/native.rb +474 -0
  197. data/lib/mxrb/runtime/sql_server_database.rb +146 -0
  198. data/lib/mxrb/runtime/toolchain.rb +82 -0
  199. data/lib/mxrb/scaffold/cli.rb +113 -0
  200. data/lib/mxrb/scaffold/generator.rb +246 -0
  201. data/lib/mxrb/scaffold/help.rb +80 -0
  202. data/lib/mxrb/scaffold/page_templates.rb +54 -0
  203. data/lib/mxrb/scaffold/recipes.rb +198 -0
  204. data/lib/mxrb/scaffold/registry.rb +82 -0
  205. data/lib/mxrb/scaffold/templates.rb +723 -0
  206. data/lib/mxrb/scaffold/transaction.rb +84 -0
  207. data/lib/mxrb/schema/tables.rb +55 -0
  208. data/lib/mxrb/semantic/analyzer.rb +473 -0
  209. data/lib/mxrb/semantic/batch_plan.rb +68 -0
  210. data/lib/mxrb/semantic/domain_mutator.rb +419 -0
  211. data/lib/mxrb/semantic/embedder.rb +51 -0
  212. data/lib/mxrb/semantic/extractor.rb +290 -0
  213. data/lib/mxrb/semantic/index.rb +623 -0
  214. data/lib/mxrb/semantic/inliner.rb +216 -0
  215. data/lib/mxrb/semantic/mover.rb +153 -0
  216. data/lib/mxrb/semantic/onnx_embedder.rb +34 -0
  217. data/lib/mxrb/semantic/remover.rb +70 -0
  218. data/lib/mxrb/semantic/renamer.rb +211 -0
  219. data/lib/mxrb/semantic/tfidf_embedder.rb +46 -0
  220. data/lib/mxrb/semantic/vec_store.rb +75 -0
  221. data/lib/mxrb/team_server.rb +390 -0
  222. data/lib/mxrb/templates/project/10.24.0.73019.json +32 -0
  223. data/lib/mxrb/templates/project/11.12.1.json +32 -0
  224. data/lib/mxrb/templates/project/6.10.8.json +50 -0
  225. data/lib/mxrb/templates/project/7.17.0.json +50 -0
  226. data/lib/mxrb/templates/project/7.5.0.json +50 -0
  227. data/lib/mxrb/templates/project/9.6.1.29396.json +50 -0
  228. data/lib/mxrb/version.rb +5 -0
  229. data/lib/mxrb/widget_package.rb +338 -0
  230. data/lib/mxrb/widget_synchronizer.rb +31 -0
  231. data/lib/mxrb/writer.rb +3775 -0
  232. data/lib/mxrb.rb +188 -0
  233. data/marketplace/catalog.json +10 -0
  234. data/marketplace/modules/shared-kernel/domain/README.md +4 -0
  235. data/marketplace/modules/shared-kernel/module.rb +5 -0
  236. data/marketplace/modules/shared-kernel/mxrb-module.json +9 -0
  237. metadata +447 -0
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mxrb
4
+ module Compiler
5
+ # Compiles Java and JavaScript action signatures consumed by the Runtime model.
6
+ class CodeActionDocumentCompiler
7
+ include ModelValues
8
+ include CodeActionTypeCompiler
9
+
10
+ TYPES = %w[JavaActions$JavaAction JavaScriptActions$JavaScriptAction].freeze
11
+
12
+ def compile(unit)
13
+ source = unit.document
14
+ case source['$Type']
15
+ when 'JavaActions$JavaAction' then java_action(source, unit.module_name)
16
+ when 'JavaScriptActions$JavaScriptAction' then javascript_action(source, unit.module_name)
17
+ else raise CompilationError, "unsupported code action #{source['$Type']}"
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def java_action(source, module_name)
24
+ {
25
+ '$ID' => source['$ID'], '$Type' => source['$Type'],
26
+ 'Parameters' => array(source['Parameters']).map { java_parameter(_1) },
27
+ 'Name' => source['Name'], 'QualifiedName' => qualified_name(module_name, source['Name']),
28
+ 'ReturnType' => code_action_type(source['JavaReturnType'])
29
+ }
30
+ end
31
+
32
+ def java_parameter(source)
33
+ {
34
+ '$ID' => source['$ID'], '$Type' => source['$Type'], 'Name' => source['Name'],
35
+ 'Type' => java_parameter_type(source['ParameterType'])
36
+ }
37
+ end
38
+
39
+ def java_parameter_type(source)
40
+ return 'String' if source['$Type'] == 'JavaActions$MicroflowJavaActionParameterType'
41
+
42
+ code_action_type(source['Type'])
43
+ end
44
+
45
+ def javascript_action(source, module_name)
46
+ {
47
+ '$ID' => source['$ID'], '$Type' => source['$Type'],
48
+ 'Parameters' => array(source['Parameters']).map { javascript_parameter(_1) },
49
+ 'Name' => source['Name'], 'QualifiedName' => qualified_name(module_name, source['Name'])
50
+ }
51
+ end
52
+
53
+ def javascript_parameter(source)
54
+ { '$ID' => source['$ID'], '$Type' => source['$Type'], 'Name' => source['Name'] }
55
+ end
56
+
57
+ def qualified_name(module_name, name)
58
+ raise CompilationError, "code action #{name} is outside a module" unless module_name
59
+
60
+ "#{module_name}.#{name}"
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mxrb
4
+ module Compiler
5
+ CodeActionMaterialization = Data.define(:model_path, :java_actions, :javascript_actions)
6
+
7
+ # Materializes server and client action signatures into model.mdp.
8
+ class CodeActionMaterializer
9
+ def initialize(mpr_path, deployment:)
10
+ @mpr_path = File.expand_path(mpr_path)
11
+ @deployment = File.expand_path(deployment)
12
+ end
13
+
14
+ def materialize
15
+ source = SourceModel.read(@mpr_path)
16
+ path = File.join(@deployment, 'model', 'model.mdp')
17
+ documents = compile_documents(source)
18
+ documents.reduce(ModelPackage.read(path)) { |package, document| package.upsert(document) }.write(path)
19
+ counts = documents.map { _1['$Type'] }.tally
20
+ CodeActionMaterialization.new(
21
+ model_path: path, java_actions: counts.fetch('JavaActions$JavaAction', 0),
22
+ javascript_actions: counts.fetch('JavaScriptActions$JavaScriptAction', 0)
23
+ )
24
+ end
25
+
26
+ private
27
+
28
+ def compile_documents(source)
29
+ compiler = CodeActionDocumentCompiler.new
30
+ source.units.select { CodeActionDocumentCompiler::TYPES.include?(_1.document['$Type']) }
31
+ .map { compiler.compile(_1) }
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mxrb
4
+ module Compiler
5
+ # Converts CodeActions type trees into Runtime type identifiers.
6
+ module CodeActionTypeCompiler
7
+ SCALARS = {
8
+ 'CodeActions$VoidType' => 'Void', 'CodeActions$StringType' => 'String',
9
+ 'CodeActions$BooleanType' => 'Boolean', 'CodeActions$IntegerType' => 'Integer',
10
+ 'CodeActions$LongType' => 'Integer', 'CodeActions$DecimalType' => 'Decimal',
11
+ 'CodeActions$FloatType' => 'Decimal', 'CodeActions$DateTimeType' => 'DateTime'
12
+ }.freeze
13
+
14
+ private
15
+
16
+ def code_action_type(source)
17
+ return 'Void' unless source
18
+
19
+ type = source['$Type'].to_s.sub(/\AJavaActions\$/, 'CodeActions$')
20
+ return SCALARS.fetch(type) if SCALARS.key?(type)
21
+
22
+ case type
23
+ when 'CodeActions$ConcreteEntityType' then source['Entity'].to_s
24
+ when 'CodeActions$EnumerationType' then "##{source['Enumeration']}"
25
+ when 'CodeActions$ParameterizedEntityType' then 'Unknown'
26
+ when 'CodeActions$ListType' then "[#{code_action_type(source['Parameter'])}]"
27
+ else raise CompilationError, "unsupported code-action type #{source['$Type']}"
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,314 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest'
4
+ require 'json'
5
+
6
+ module Mxrb
7
+ module Compiler
8
+ # Compiles association- and database-backed instances of the official React Combo box widget.
9
+ # rubocop:disable Metrics
10
+ class ComboBoxBundleCompiler
11
+ include ModelValues
12
+
13
+ WIDGET_ID = 'com.mendix.widget.web.combobox.Combobox'
14
+
15
+ def initialize(source, page_name, widget, scope:, entity:)
16
+ @source = source
17
+ @page_name = page_name
18
+ @widget = widget
19
+ @scope = scope
20
+ @entity = entity
21
+ @index = document_index
22
+ @values = property_values(widget['Object'])
23
+ @data_source = WebListDataSource.new(source, widget)
24
+ end
25
+
26
+ def supported?
27
+ widget_type&.fetch('WidgetId', nil) == WIDGET_ID && supported_source? && resolved_scope
28
+ end
29
+
30
+ def render # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
31
+ properties = primitive_properties.merge(
32
+ key: widget_key, '$widgetId': widget_key, class: css_class, id: widget_key,
33
+ optionsSourceStaticDataSource: [],
34
+ ariaRequired: raw(expression(false))
35
+ )
36
+ properties.merge!(source_properties)
37
+ control = "React.createElement($Combobox, #{javascript(properties)})"
38
+ caption = translated_text(@widget['LabelTemplate'])
39
+ group = {
40
+ key: "#{widget_key}$formGroup", '$widgetId': "#{widget_key}$formGroup",
41
+ class: "#{css_class} mx-combobox", control: raw("[#{control}]"),
42
+ width: 3, orientation: 'horizontal', labelFor: widget_key,
43
+ caption: raw(expression(caption)), hasError: raw(expression(false))
44
+ }
45
+ "React.createElement($FormGroup, #{javascript(group)})"
46
+ end # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
47
+
48
+ private
49
+
50
+ def supported_source?
51
+ return database_source_supported? if primitive('source') == 'database'
52
+ return enumeration_source_supported? if primitive('optionsSourceType') == 'enumeration'
53
+
54
+ association_source_supported?
55
+ end
56
+
57
+ def database_source_supported?
58
+ target_attribute && database_caption_attribute && database_value_attribute && list_source_supported?
59
+ end
60
+
61
+ def association_source_supported?
62
+ primitive('source') == 'context' && primitive('optionsSourceType') == 'association' &&
63
+ association_step && (association_caption_attribute || association_caption_expression) &&
64
+ list_source_supported?
65
+ end
66
+
67
+ def enumeration_source_supported?
68
+ primitive('source') == 'context' && enumeration_attribute
69
+ end
70
+
71
+ def list_source_supported?
72
+ @data_source.supported? && !@data_source.entity.to_s.empty?
73
+ end
74
+
75
+ def source_properties
76
+ return database_properties if primitive('source') == 'database'
77
+ return enumeration_properties if primitive('optionsSourceType') == 'enumeration'
78
+
79
+ association_properties
80
+ end
81
+
82
+ def enumeration_properties
83
+ { attributeEnumeration: raw(attribute_property(enumeration_attribute)) }
84
+ end
85
+
86
+ def database_properties
87
+ return database_association_properties if target_steps.any?
88
+
89
+ {
90
+ databaseAttributeString: raw(attribute_property(target_attribute)),
91
+ optionsSourceDatabaseCaptionAttribute: raw(list_attribute_property(database_caption_attribute)),
92
+ optionsSourceDatabaseValueAttribute: raw(list_attribute_property(database_value_attribute)),
93
+ optionsSourceDatabaseDataSource: raw(list_property),
94
+ optionsSourceDatabaseItemSelection: raw(selection_property)
95
+ }
96
+ end
97
+
98
+ def database_association_properties
99
+ {
100
+ source: 'context',
101
+ optionsSourceAssociationCaptionAttribute: raw(list_attribute_property(database_caption_attribute)),
102
+ attributeAssociation: raw(association_property(target_steps.first)),
103
+ optionsSourceAssociationDataSource: raw(list_property)
104
+ }
105
+ end
106
+
107
+ def association_properties
108
+ properties = {
109
+ attributeAssociation: raw(association_property(association_step)),
110
+ optionsSourceAssociationDataSource: raw(list_property)
111
+ }
112
+ if association_caption_attribute
113
+ properties[:optionsSourceAssociationCaptionAttribute] = raw(
114
+ list_attribute_property(association_caption_attribute)
115
+ )
116
+ else
117
+ properties[:optionsSourceAssociationCaptionExpression] = raw(list_expression_property)
118
+ end
119
+ properties
120
+ end
121
+
122
+ def list_expression_property
123
+ source = value('optionsSourceAssociationCaptionExpression')&.fetch('Expression', '').to_s
124
+ path = source[%r{\A\$currentObject/(.+)\z}, 1]
125
+ "ListExpressionProperty(#{javascript(
126
+ expression: {
127
+ expr: { type: 'variable', variable: 'currentObject', path: },
128
+ args: { currentObject: { widget: widget_key, source: 'object' } }
129
+ },
130
+ dataSourceId: data_source_id
131
+ )})"
132
+ end
133
+
134
+ def association_property(step)
135
+ "AssociationProperty(#{javascript(
136
+ type: 'Reference', entity: resolved_entity, path: '', attribute: step['Association'],
137
+ endpointEntity: step['DestinationEntity'], selectableObjectsId: data_source_id,
138
+ scope: resolved_scope, onChange: do_nothing
139
+ )})"
140
+ end
141
+
142
+ def list_property
143
+ config = {
144
+ dataSourceId: data_source_id, entity: @data_source.entity, scope: resolved_scope,
145
+ operationId: WebOperationCompiler.operation_id(@page_name, @widget['Name'])
146
+ }
147
+ if @data_source.xpath?
148
+ "DatabaseObjectListProperty(#{javascript(config.merge(sort: []))})"
149
+ else
150
+ "MicroflowObjectListProperty(#{javascript(config.merge(argMap: {}, fetchOnlyWithAllParams: false))})"
151
+ end
152
+ end
153
+
154
+ def selection_property
155
+ selection = value('optionsSourceDatabaseItemSelection')&.fetch('Selection', 'Single') || 'Single'
156
+ "SelectionProperty(#{javascript(selectionType: selection, dataSourceId: data_source_id)})"
157
+ end
158
+
159
+ def attribute_property(attribute)
160
+ entity, name = split_attribute(attribute.fetch('Attribute'))
161
+ path = entity_steps(attribute).flat_map do |step|
162
+ [step['Association'], step['DestinationEntity']]
163
+ end.join('/')
164
+ config = {
165
+ scope: resolved_scope, path:, entity:, attribute: name, onChange: do_nothing,
166
+ isList: false, validation: nil, formatting: {}
167
+ }
168
+ "AttributeProperty(#{javascript(config)})"
169
+ end
170
+
171
+ def list_attribute_property(attribute)
172
+ entity, name = split_attribute(attribute.fetch('Attribute'))
173
+ "ListAttributeProperty(#{javascript(
174
+ path: '', entity:, attribute: name, attributeType: 'String', sortable: true,
175
+ filterable: true, dataSourceId: data_source_id, isList: false
176
+ )})"
177
+ end
178
+
179
+ def split_attribute(qualified)
180
+ entity, separator, name = qualified.to_s.rpartition('.')
181
+ raise CompilationError, "invalid Combo box attribute #{qualified.inspect}" unless separator == '.'
182
+
183
+ [entity, name]
184
+ end
185
+
186
+ def resolved_scope
187
+ @resolved_scope ||= begin
188
+ parameter = target_value&.dig('SourceVariable', 'PageParameter').to_s
189
+ parameter.empty? ? @scope : "$#{parameter}"
190
+ end
191
+ end
192
+
193
+ def resolved_entity
194
+ return @entity unless @entity.to_s.empty?
195
+
196
+ parameter = target_value&.dig('SourceVariable', 'PageParameter').to_s
197
+ page_parameter_entity(parameter)
198
+ end
199
+
200
+ def page_parameter_entity(name)
201
+ module_name, document_name = @page_name.split('.', 2)
202
+ page = @source.units_of('Forms$Page').find do |unit|
203
+ unit.module_name == module_name && unit.document['Name'] == document_name
204
+ end
205
+ parameter = array(page&.document&.fetch('Parameters', nil)).find { _1['Name'] == name }
206
+ parameter&.dig('ParameterType', 'Entity').to_s
207
+ end
208
+
209
+ def target_value = value('databaseAttributeString')
210
+ def target_attribute = target_value&.fetch('AttributeRef', nil)
211
+ def enumeration_attribute = value('attributeEnumeration')&.fetch('AttributeRef', nil)
212
+ def database_caption_attribute = value('optionsSourceDatabaseCaptionAttribute')&.fetch('AttributeRef', nil)
213
+ def database_value_attribute = value('optionsSourceDatabaseValueAttribute')&.fetch('AttributeRef', nil)
214
+ def association_caption_attribute = value('optionsSourceAssociationCaptionAttribute')&.fetch('AttributeRef', nil)
215
+ def association_caption_expression = value('optionsSourceAssociationCaptionExpression')&.fetch('Expression', nil)
216
+
217
+ def association_step
218
+ entity_steps(value('attributeAssociation')).first
219
+ end
220
+
221
+ def target_steps = entity_steps(target_attribute)
222
+
223
+ def entity_steps(value)
224
+ array(value&.dig('EntityRef', 'Steps'))
225
+ end
226
+
227
+ def primitive_properties
228
+ @values.each_with_object({}) do |(key, (type, property)), result|
229
+ compiled = compile_primitive(type, property)
230
+ result[key.to_sym] = compiled unless compiled.nil?
231
+ end
232
+ end
233
+
234
+ def compile_primitive(type, property)
235
+ case type
236
+ when 'Boolean' then property['PrimitiveValue'] == 'true'
237
+ when 'Integer' then property['PrimitiveValue'].to_i
238
+ when 'Enumeration' then property['PrimitiveValue'].to_s
239
+ when 'TextTemplate' then raw(expression(translated_text(property['TextTemplate'])))
240
+ when 'Widgets' then [] if array(property['Widgets']).empty?
241
+ end
242
+ end
243
+
244
+ def translated_text(template)
245
+ items = array(template&.dig('Template', 'Items'))
246
+ items.find { _1['LanguageCode'] == 'en_US' }&.fetch('Text', '') ||
247
+ items.first&.fetch('Text', '') || ''
248
+ end
249
+
250
+ def expression(value)
251
+ "ExpressionProperty(#{javascript(expression: { expr: { type: 'literal', value: }, args: {} })})"
252
+ end
253
+
254
+ def do_nothing
255
+ { type: 'doNothing', argMap: {}, config: {}, disabledDuringExecution: false }
256
+ end
257
+
258
+ def data_source_id = "p.#{Digest::SHA256.hexdigest(widget_key)[0, 6].to_i(16)}"
259
+ def widget_key = "p.#{@page_name}.#{@widget['Name']}"
260
+
261
+ def css_class
262
+ ["mx-name-#{@widget['Name']}", @widget.dig('Appearance', 'Class')]
263
+ .map(&:to_s).reject(&:empty?).uniq.join(' ')
264
+ end
265
+
266
+ def primitive(key) = value(key)&.fetch('PrimitiveValue', nil)
267
+ def value(key) = @values[key]&.last
268
+ def raw(value) = { '$raw' => value }
269
+
270
+ def javascript(value)
271
+ return value['$raw'] if value.is_a?(Hash) && value.key?('$raw')
272
+ return "[#{value.map { javascript(_1) }.join(', ')}]" if value.is_a?(Array)
273
+ if value.is_a?(Hash)
274
+ return "{ #{value.map { |key, item| "#{JSON.generate(key)}: #{javascript(item)}" }.join(', ')} }"
275
+ end
276
+
277
+ JSON.generate(value)
278
+ end
279
+
280
+ def property_values(object)
281
+ array(object&.fetch('Properties', nil)).filter_map do |property|
282
+ type = @index[IO::BsonCodec.extract_id(property['TypePointer'])]
283
+ next unless type
284
+
285
+ [type.fetch('PropertyKey'), [type.dig('ValueType', 'Type'), property['Value']]]
286
+ end.to_h
287
+ end
288
+
289
+ def widget_type
290
+ object_type_id = IO::BsonCodec.extract_id(@widget.dig('Object', 'TypePointer'))
291
+ @index.values.find do |document|
292
+ IO::BsonCodec.extract_id(document.dig('ObjectType', '$ID')) == object_type_id
293
+ end
294
+ end
295
+
296
+ def document_index
297
+ return @source.document_index if @source.is_a?(SourceModel)
298
+
299
+ {}.tap { |index| @source.documents.each { index_document(_1, index) } }
300
+ end
301
+
302
+ def index_document(value, index)
303
+ case value
304
+ when Hash
305
+ id = IO::BsonCodec.extract_id(value['$ID'])
306
+ index[id] = value if id
307
+ value.each_value { index_document(_1, index) }
308
+ when Array then value.each { index_document(_1, index) }
309
+ end
310
+ end
311
+ end
312
+ # rubocop:enable Metrics
313
+ end
314
+ end
@@ -0,0 +1,169 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mxrb
4
+ module Compiler
5
+ CompatibilityFinding = Data.define(:severity, :category, :type, :location, :message, :count) do
6
+ def to_h = { severity:, category:, type:, location:, message:, count: }
7
+ end
8
+
9
+ CompatibilityReport = Data.define(:path, :mendix_version, :findings, :stats) do
10
+ def compatible? = findings.none? { _1.severity == :error }
11
+ def errors = findings.select { _1.severity == :error }
12
+ def warnings = findings.select { _1.severity == :warning }
13
+
14
+ def to_h
15
+ {
16
+ path:, mendix_version:, compatible: compatible?, stats:,
17
+ findings: findings.map(&:to_h)
18
+ }
19
+ end
20
+ end
21
+
22
+ # Read-only preflight for features consumed by the native compiler and web runtime.
23
+ # rubocop:disable Metrics
24
+ class CompatibilityAnalyzer
25
+ include ModelValues
26
+
27
+ def initialize(path, source: nil)
28
+ @path = File.expand_path(path)
29
+ @source = source || SourceModel.read(@path)
30
+ @findings = []
31
+ end
32
+
33
+ def analyze
34
+ analyze_version
35
+ analyze_features if @adapter
36
+ CompatibilityReport.new(
37
+ path: @path, mendix_version: @source.version, findings: collapsed_findings.freeze,
38
+ stats: statistics.freeze
39
+ )
40
+ end
41
+
42
+ private
43
+
44
+ def analyze_features
45
+ if @adapter.web_profiles.include?(:react)
46
+ analyze_pages
47
+ analyze_layouts
48
+ analyze_nanoflows
49
+ else
50
+ analyze_legacy_pages
51
+ end
52
+ end
53
+
54
+ def analyze_version
55
+ @adapter = Adapter.for(@source.version, source: @source)
56
+ rescue UnsupportedVersion => e
57
+ add(:error, :version, "Mendix #{@source.version}", 'project', e.message)
58
+ end
59
+
60
+ def analyze_legacy_pages
61
+ LegacyPageBuilder.new(@source, '', profiles: @adapter.web_profiles).audit.each do |location, types|
62
+ types.each do |type|
63
+ add(:error, page_category(type), type, location,
64
+ "#{type} is not compiled by the native legacy web renderer")
65
+ end
66
+ end
67
+ rescue CompilationError, KeyError, TypeError => e
68
+ add(:error, :page, 'Forms$Page', 'project', e.message)
69
+ end
70
+
71
+ def analyze_pages
72
+ @source.units_of('Forms$Page').select { web_page?(_1) }.each do |unit|
73
+ location = qualified_name(unit)
74
+ bundle = PageBundleCompiler.new(@source).compile(unit)
75
+ bundle.unsupported_widgets.each do |type|
76
+ if type == 'CustomWidgets$CustomWidget'
77
+ bundle.unsupported_custom_widgets.each do |identifier|
78
+ add(:error, :custom_widget, identifier, location,
79
+ "#{identifier} is not compiled by the native web renderer")
80
+ end
81
+ else
82
+ add(:error, page_category(type), type, location,
83
+ "#{type} is not compiled by the native web renderer")
84
+ end
85
+ end
86
+ rescue CompilationError, KeyError, TypeError => e
87
+ add(:error, :page, 'Forms$Page', location, e.message)
88
+ end
89
+ end
90
+
91
+ def analyze_nanoflows
92
+ compiler = NanoflowProgramCompiler.new(@source)
93
+ @source.units_of('Microflows$Nanoflow').each { compiler.reference(qualified_name(_1)) }
94
+ compiler.unsupported.each do |entry|
95
+ location, type = entry.split(':', 2)
96
+ add(:error, :nanoflow, type, location, "#{type} is not compiled by the native nanoflow runtime")
97
+ end
98
+ end
99
+
100
+ def analyze_layouts
101
+ @source.units_of('Forms$Layout').select { web_layout?(_1) }.each do |unit|
102
+ location = qualified_name(unit)
103
+ bundle = PageBundleCompiler.new(@source).compile_layout(unit)
104
+ add_bundle_findings(bundle, location)
105
+ rescue CompilationError, KeyError, TypeError => e
106
+ add(:error, :layout, 'Forms$Layout', location, e.message)
107
+ end
108
+ end
109
+
110
+ def add_bundle_findings(bundle, location)
111
+ bundle.unsupported_widgets.each do |type|
112
+ if type == 'CustomWidgets$CustomWidget'
113
+ bundle.unsupported_custom_widgets.each do |identifier|
114
+ add(:error, :custom_widget, identifier, location,
115
+ "#{identifier} is not compiled by the native web renderer")
116
+ end
117
+ else
118
+ add(:error, page_category(type), type, location,
119
+ "#{type} is not compiled by the native web renderer")
120
+ end
121
+ end
122
+ end
123
+
124
+ def page_category(type)
125
+ return :client_action if type.end_with?('Action')
126
+ return :custom_widget if type == 'CustomWidgets$CustomWidget'
127
+
128
+ :widget
129
+ end
130
+
131
+ def web_page?(unit) = !@source.is_a?(SourceModel) || @source.web_page?(unit)
132
+ def web_layout?(unit) = !@source.is_a?(SourceModel) || @source.web_layout?(unit)
133
+
134
+ def add(severity, category, type, location, message)
135
+ @findings << CompatibilityFinding.new(
136
+ severity:, category:, type: type.to_s, location: location.to_s, message:, count: 1
137
+ )
138
+ end
139
+
140
+ def collapsed_findings
141
+ grouped = @findings.group_by { finding_key(_1) }
142
+ findings = grouped.map { |key, matches| collapsed_finding(key, matches.size) }
143
+ findings.sort_by { [_1.severity.to_s, _1.category.to_s, _1.location, _1.type] }
144
+ end
145
+
146
+ def finding_key(finding)
147
+ [finding.severity, finding.category, finding.type, finding.location, finding.message]
148
+ end
149
+
150
+ def collapsed_finding(key, count)
151
+ severity, category, type, location, message = key
152
+ CompatibilityFinding.new(severity:, category:, type:, location:, message:, count:)
153
+ end
154
+
155
+ def statistics
156
+ {
157
+ units: @source.units.size,
158
+ pages: @source.units_of('Forms$Page').size,
159
+ layouts: @source.units_of('Forms$Layout').size,
160
+ nanoflows: @source.units_of('Microflows$Nanoflow').size,
161
+ microflows: @source.units_of('Microflows$Microflow').size
162
+ }
163
+ end
164
+
165
+ def qualified_name(unit) = [unit.module_name, unit.document['Name']].compact.join('.')
166
+ end
167
+ # rubocop:enable Metrics
168
+ end
169
+ end