@memberjunction/react-linter 0.0.1 → 5.38.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 (330) hide show
  1. package/dist/component-linter.d.ts +77 -0
  2. package/dist/component-linter.d.ts.map +1 -0
  3. package/dist/component-linter.js +1206 -0
  4. package/dist/component-linter.js.map +1 -0
  5. package/dist/control-flow-analyzer.d.ts +184 -0
  6. package/dist/control-flow-analyzer.d.ts.map +1 -0
  7. package/dist/control-flow-analyzer.js +798 -0
  8. package/dist/control-flow-analyzer.js.map +1 -0
  9. package/dist/index.d.ts +6 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +8 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/library-lint-cache.d.ts +50 -0
  14. package/dist/library-lint-cache.d.ts.map +1 -0
  15. package/dist/library-lint-cache.js +173 -0
  16. package/dist/library-lint-cache.js.map +1 -0
  17. package/dist/lint-rule.d.ts +70 -0
  18. package/dist/lint-rule.d.ts.map +1 -0
  19. package/dist/lint-rule.js +30 -0
  20. package/dist/lint-rule.js.map +1 -0
  21. package/dist/lint-utils.d.ts +131 -0
  22. package/dist/lint-utils.d.ts.map +1 -0
  23. package/dist/lint-utils.js +358 -0
  24. package/dist/lint-utils.js.map +1 -0
  25. package/dist/linter-options.d.ts +51 -0
  26. package/dist/linter-options.d.ts.map +1 -0
  27. package/dist/linter-options.js +2 -0
  28. package/dist/linter-options.js.map +1 -0
  29. package/dist/prop-value-extractor.d.ts +147 -0
  30. package/dist/prop-value-extractor.d.ts.map +1 -0
  31. package/dist/prop-value-extractor.js +472 -0
  32. package/dist/prop-value-extractor.js.map +1 -0
  33. package/dist/runtime-rules/ai-tools-availability-check.d.ts +9 -0
  34. package/dist/runtime-rules/ai-tools-availability-check.d.ts.map +1 -0
  35. package/dist/runtime-rules/ai-tools-availability-check.js +223 -0
  36. package/dist/runtime-rules/ai-tools-availability-check.js.map +1 -0
  37. package/dist/runtime-rules/callback-event-validation.d.ts +22 -0
  38. package/dist/runtime-rules/callback-event-validation.d.ts.map +1 -0
  39. package/dist/runtime-rules/callback-event-validation.js +561 -0
  40. package/dist/runtime-rules/callback-event-validation.js.map +1 -0
  41. package/dist/runtime-rules/chart-field-validation.d.ts +10 -0
  42. package/dist/runtime-rules/chart-field-validation.d.ts.map +1 -0
  43. package/dist/runtime-rules/chart-field-validation.js +270 -0
  44. package/dist/runtime-rules/chart-field-validation.js.map +1 -0
  45. package/dist/runtime-rules/child-component-prop-validation.d.ts +11 -0
  46. package/dist/runtime-rules/child-component-prop-validation.d.ts.map +1 -0
  47. package/dist/runtime-rules/child-component-prop-validation.js +443 -0
  48. package/dist/runtime-rules/child-component-prop-validation.js.map +1 -0
  49. package/dist/runtime-rules/component-name-mismatch.d.ts +19 -0
  50. package/dist/runtime-rules/component-name-mismatch.d.ts.map +1 -0
  51. package/dist/runtime-rules/component-name-mismatch.js +82 -0
  52. package/dist/runtime-rules/component-name-mismatch.js.map +1 -0
  53. package/dist/runtime-rules/component-not-in-dependencies.d.ts +20 -0
  54. package/dist/runtime-rules/component-not-in-dependencies.d.ts.map +1 -0
  55. package/dist/runtime-rules/component-not-in-dependencies.js +92 -0
  56. package/dist/runtime-rules/component-not-in-dependencies.js.map +1 -0
  57. package/dist/runtime-rules/component-props-validation.d.ts +25 -0
  58. package/dist/runtime-rules/component-props-validation.d.ts.map +1 -0
  59. package/dist/runtime-rules/component-props-validation.js +228 -0
  60. package/dist/runtime-rules/component-props-validation.js.map +1 -0
  61. package/dist/runtime-rules/component-usage-without-destructuring.d.ts +20 -0
  62. package/dist/runtime-rules/component-usage-without-destructuring.d.ts.map +1 -0
  63. package/dist/runtime-rules/component-usage-without-destructuring.js +124 -0
  64. package/dist/runtime-rules/component-usage-without-destructuring.js.map +1 -0
  65. package/dist/runtime-rules/data-result-validation.d.ts +9 -0
  66. package/dist/runtime-rules/data-result-validation.d.ts.map +1 -0
  67. package/dist/runtime-rules/data-result-validation.js +763 -0
  68. package/dist/runtime-rules/data-result-validation.js.map +1 -0
  69. package/dist/runtime-rules/datagrid-field-validation.d.ts +10 -0
  70. package/dist/runtime-rules/datagrid-field-validation.d.ts.map +1 -0
  71. package/dist/runtime-rules/datagrid-field-validation.js +249 -0
  72. package/dist/runtime-rules/datagrid-field-validation.js.map +1 -0
  73. package/dist/runtime-rules/dependency-shadowing.d.ts +20 -0
  74. package/dist/runtime-rules/dependency-shadowing.d.ts.map +1 -0
  75. package/dist/runtime-rules/dependency-shadowing.js +147 -0
  76. package/dist/runtime-rules/dependency-shadowing.js.map +1 -0
  77. package/dist/runtime-rules/entity-field-access-validation.d.ts +12 -0
  78. package/dist/runtime-rules/entity-field-access-validation.d.ts.map +1 -0
  79. package/dist/runtime-rules/entity-field-access-validation.js +304 -0
  80. package/dist/runtime-rules/entity-field-access-validation.js.map +1 -0
  81. package/dist/runtime-rules/event-parameter-validation.d.ts +22 -0
  82. package/dist/runtime-rules/event-parameter-validation.d.ts.map +1 -0
  83. package/dist/runtime-rules/event-parameter-validation.js +406 -0
  84. package/dist/runtime-rules/event-parameter-validation.js.map +1 -0
  85. package/dist/runtime-rules/index.d.ts +61 -0
  86. package/dist/runtime-rules/index.d.ts.map +1 -0
  87. package/dist/runtime-rules/index.js +62 -0
  88. package/dist/runtime-rules/index.js.map +1 -0
  89. package/dist/runtime-rules/library-variable-names.d.ts +24 -0
  90. package/dist/runtime-rules/library-variable-names.d.ts.map +1 -0
  91. package/dist/runtime-rules/library-variable-names.js +88 -0
  92. package/dist/runtime-rules/library-variable-names.js.map +1 -0
  93. package/dist/runtime-rules/no-child-implementation.d.ts +18 -0
  94. package/dist/runtime-rules/no-child-implementation.d.ts.map +1 -0
  95. package/dist/runtime-rules/no-child-implementation.js +57 -0
  96. package/dist/runtime-rules/no-child-implementation.js.map +1 -0
  97. package/dist/runtime-rules/no-data-prop.d.ts +22 -0
  98. package/dist/runtime-rules/no-data-prop.d.ts.map +1 -0
  99. package/dist/runtime-rules/no-data-prop.js +111 -0
  100. package/dist/runtime-rules/no-data-prop.js.map +1 -0
  101. package/dist/runtime-rules/no-export-statements.d.ts +18 -0
  102. package/dist/runtime-rules/no-export-statements.d.ts.map +1 -0
  103. package/dist/runtime-rules/no-export-statements.js +143 -0
  104. package/dist/runtime-rules/no-export-statements.js.map +1 -0
  105. package/dist/runtime-rules/no-iife-wrapper.d.ts +18 -0
  106. package/dist/runtime-rules/no-iife-wrapper.d.ts.map +1 -0
  107. package/dist/runtime-rules/no-iife-wrapper.js +217 -0
  108. package/dist/runtime-rules/no-iife-wrapper.js.map +1 -0
  109. package/dist/runtime-rules/no-import-statements.d.ts +18 -0
  110. package/dist/runtime-rules/no-import-statements.d.ts.map +1 -0
  111. package/dist/runtime-rules/no-import-statements.js +65 -0
  112. package/dist/runtime-rules/no-import-statements.js.map +1 -0
  113. package/dist/runtime-rules/no-react-destructuring.d.ts +18 -0
  114. package/dist/runtime-rules/no-react-destructuring.d.ts.map +1 -0
  115. package/dist/runtime-rules/no-react-destructuring.js +60 -0
  116. package/dist/runtime-rules/no-react-destructuring.js.map +1 -0
  117. package/dist/runtime-rules/no-require-statements.d.ts +18 -0
  118. package/dist/runtime-rules/no-require-statements.d.ts.map +1 -0
  119. package/dist/runtime-rules/no-require-statements.js +109 -0
  120. package/dist/runtime-rules/no-require-statements.js.map +1 -0
  121. package/dist/runtime-rules/no-return-component.d.ts +18 -0
  122. package/dist/runtime-rules/no-return-component.d.ts.map +1 -0
  123. package/dist/runtime-rules/no-return-component.js +106 -0
  124. package/dist/runtime-rules/no-return-component.js.map +1 -0
  125. package/dist/runtime-rules/no-unwrap-utility-libs.d.ts +20 -0
  126. package/dist/runtime-rules/no-unwrap-utility-libs.d.ts.map +1 -0
  127. package/dist/runtime-rules/no-unwrap-utility-libs.js +75 -0
  128. package/dist/runtime-rules/no-unwrap-utility-libs.js.map +1 -0
  129. package/dist/runtime-rules/no-use-reducer.d.ts +19 -0
  130. package/dist/runtime-rules/no-use-reducer.d.ts.map +1 -0
  131. package/dist/runtime-rules/no-use-reducer.js +78 -0
  132. package/dist/runtime-rules/no-use-reducer.js.map +1 -0
  133. package/dist/runtime-rules/no-window-access.d.ts +23 -0
  134. package/dist/runtime-rules/no-window-access.d.ts.map +1 -0
  135. package/dist/runtime-rules/no-window-access.js +136 -0
  136. package/dist/runtime-rules/no-window-access.js.map +1 -0
  137. package/dist/runtime-rules/noisy-settings-updates.d.ts +18 -0
  138. package/dist/runtime-rules/noisy-settings-updates.d.ts.map +1 -0
  139. package/dist/runtime-rules/noisy-settings-updates.js +110 -0
  140. package/dist/runtime-rules/noisy-settings-updates.js.map +1 -0
  141. package/dist/runtime-rules/overflow-hidden-on-layout-container.d.ts +30 -0
  142. package/dist/runtime-rules/overflow-hidden-on-layout-container.d.ts.map +1 -0
  143. package/dist/runtime-rules/overflow-hidden-on-layout-container.js +220 -0
  144. package/dist/runtime-rules/overflow-hidden-on-layout-container.js.map +1 -0
  145. package/dist/runtime-rules/pass-standard-props.d.ts +19 -0
  146. package/dist/runtime-rules/pass-standard-props.d.ts.map +1 -0
  147. package/dist/runtime-rules/pass-standard-props.js +82 -0
  148. package/dist/runtime-rules/pass-standard-props.js.map +1 -0
  149. package/dist/runtime-rules/prefer-async-await.d.ts +17 -0
  150. package/dist/runtime-rules/prefer-async-await.d.ts.map +1 -0
  151. package/dist/runtime-rules/prefer-async-await.js +52 -0
  152. package/dist/runtime-rules/prefer-async-await.js.map +1 -0
  153. package/dist/runtime-rules/prefer-jsx-syntax.d.ts +17 -0
  154. package/dist/runtime-rules/prefer-jsx-syntax.d.ts.map +1 -0
  155. package/dist/runtime-rules/prefer-jsx-syntax.js +51 -0
  156. package/dist/runtime-rules/prefer-jsx-syntax.js.map +1 -0
  157. package/dist/runtime-rules/prop-state-sync.d.ts +19 -0
  158. package/dist/runtime-rules/prop-state-sync.d.ts.map +1 -0
  159. package/dist/runtime-rules/prop-state-sync.js +76 -0
  160. package/dist/runtime-rules/prop-state-sync.js.map +1 -0
  161. package/dist/runtime-rules/property-name-consistency.d.ts +20 -0
  162. package/dist/runtime-rules/property-name-consistency.d.ts.map +1 -0
  163. package/dist/runtime-rules/property-name-consistency.js +172 -0
  164. package/dist/runtime-rules/property-name-consistency.js.map +1 -0
  165. package/dist/runtime-rules/query-result-field-access-validation.d.ts +10 -0
  166. package/dist/runtime-rules/query-result-field-access-validation.d.ts.map +1 -0
  167. package/dist/runtime-rules/query-result-field-access-validation.js +304 -0
  168. package/dist/runtime-rules/query-result-field-access-validation.js.map +1 -0
  169. package/dist/runtime-rules/react-component-naming.d.ts +19 -0
  170. package/dist/runtime-rules/react-component-naming.d.ts.map +1 -0
  171. package/dist/runtime-rules/react-component-naming.js +72 -0
  172. package/dist/runtime-rules/react-component-naming.js.map +1 -0
  173. package/dist/runtime-rules/react-hooks-rules.d.ts +27 -0
  174. package/dist/runtime-rules/react-hooks-rules.d.ts.map +1 -0
  175. package/dist/runtime-rules/react-hooks-rules.js +223 -0
  176. package/dist/runtime-rules/react-hooks-rules.js.map +1 -0
  177. package/dist/runtime-rules/required-queries-not-called.d.ts +19 -0
  178. package/dist/runtime-rules/required-queries-not-called.d.ts.map +1 -0
  179. package/dist/runtime-rules/required-queries-not-called.js +146 -0
  180. package/dist/runtime-rules/required-queries-not-called.js.map +1 -0
  181. package/dist/runtime-rules/runquery-call-validation.d.ts +11 -0
  182. package/dist/runtime-rules/runquery-call-validation.d.ts.map +1 -0
  183. package/dist/runtime-rules/runquery-call-validation.js +886 -0
  184. package/dist/runtime-rules/runquery-call-validation.js.map +1 -0
  185. package/dist/runtime-rules/runview-call-validation.d.ts +10 -0
  186. package/dist/runtime-rules/runview-call-validation.d.ts.map +1 -0
  187. package/dist/runtime-rules/runview-call-validation.js +336 -0
  188. package/dist/runtime-rules/runview-call-validation.js.map +1 -0
  189. package/dist/runtime-rules/saved-user-settings-pattern.d.ts +19 -0
  190. package/dist/runtime-rules/saved-user-settings-pattern.d.ts.map +1 -0
  191. package/dist/runtime-rules/saved-user-settings-pattern.js +90 -0
  192. package/dist/runtime-rules/saved-user-settings-pattern.js.map +1 -0
  193. package/dist/runtime-rules/search-availability-check.d.ts +9 -0
  194. package/dist/runtime-rules/search-availability-check.d.ts.map +1 -0
  195. package/dist/runtime-rules/search-availability-check.js +220 -0
  196. package/dist/runtime-rules/search-availability-check.js.map +1 -0
  197. package/dist/runtime-rules/search-call-validation.d.ts +9 -0
  198. package/dist/runtime-rules/search-call-validation.d.ts.map +1 -0
  199. package/dist/runtime-rules/search-call-validation.js +336 -0
  200. package/dist/runtime-rules/search-call-validation.js.map +1 -0
  201. package/dist/runtime-rules/server-reload-on-client-operation.d.ts +18 -0
  202. package/dist/runtime-rules/server-reload-on-client-operation.d.ts.map +1 -0
  203. package/dist/runtime-rules/server-reload-on-client-operation.js +107 -0
  204. package/dist/runtime-rules/server-reload-on-client-operation.js.map +1 -0
  205. package/dist/runtime-rules/single-function-only.d.ts +18 -0
  206. package/dist/runtime-rules/single-function-only.d.ts.map +1 -0
  207. package/dist/runtime-rules/single-function-only.js +103 -0
  208. package/dist/runtime-rules/single-function-only.js.map +1 -0
  209. package/dist/runtime-rules/string-replace-all-occurrences.d.ts +19 -0
  210. package/dist/runtime-rules/string-replace-all-occurrences.d.ts.map +1 -0
  211. package/dist/runtime-rules/string-replace-all-occurrences.js +109 -0
  212. package/dist/runtime-rules/string-replace-all-occurrences.js.map +1 -0
  213. package/dist/runtime-rules/string-template-validation.d.ts +22 -0
  214. package/dist/runtime-rules/string-template-validation.d.ts.map +1 -0
  215. package/dist/runtime-rules/string-template-validation.js +163 -0
  216. package/dist/runtime-rules/string-template-validation.js.map +1 -0
  217. package/dist/runtime-rules/styles-validation.d.ts +10 -0
  218. package/dist/runtime-rules/styles-validation.d.ts.map +1 -0
  219. package/dist/runtime-rules/styles-validation.js +153 -0
  220. package/dist/runtime-rules/styles-validation.js.map +1 -0
  221. package/dist/runtime-rules/type-inference-errors.d.ts +23 -0
  222. package/dist/runtime-rules/type-inference-errors.d.ts.map +1 -0
  223. package/dist/runtime-rules/type-inference-errors.js +53 -0
  224. package/dist/runtime-rules/type-inference-errors.js.map +1 -0
  225. package/dist/runtime-rules/type-mismatch-operation.d.ts +23 -0
  226. package/dist/runtime-rules/type-mismatch-operation.d.ts.map +1 -0
  227. package/dist/runtime-rules/type-mismatch-operation.js +145 -0
  228. package/dist/runtime-rules/type-mismatch-operation.js.map +1 -0
  229. package/dist/runtime-rules/undefined-component-usage.d.ts +20 -0
  230. package/dist/runtime-rules/undefined-component-usage.d.ts.map +1 -0
  231. package/dist/runtime-rules/undefined-component-usage.js +138 -0
  232. package/dist/runtime-rules/undefined-component-usage.js.map +1 -0
  233. package/dist/runtime-rules/undefined-jsx-component.d.ts +25 -0
  234. package/dist/runtime-rules/undefined-jsx-component.d.ts.map +1 -0
  235. package/dist/runtime-rules/undefined-jsx-component.js +269 -0
  236. package/dist/runtime-rules/undefined-jsx-component.js.map +1 -0
  237. package/dist/runtime-rules/unsafe-array-operations.d.ts +25 -0
  238. package/dist/runtime-rules/unsafe-array-operations.d.ts.map +1 -0
  239. package/dist/runtime-rules/unsafe-array-operations.js +347 -0
  240. package/dist/runtime-rules/unsafe-array-operations.js.map +1 -0
  241. package/dist/runtime-rules/unsafe-formatting-methods.d.ts +24 -0
  242. package/dist/runtime-rules/unsafe-formatting-methods.d.ts.map +1 -0
  243. package/dist/runtime-rules/unsafe-formatting-methods.js +277 -0
  244. package/dist/runtime-rules/unsafe-formatting-methods.js.map +1 -0
  245. package/dist/runtime-rules/unused-component-dependencies.d.ts +19 -0
  246. package/dist/runtime-rules/unused-component-dependencies.d.ts.map +1 -0
  247. package/dist/runtime-rules/unused-component-dependencies.js +90 -0
  248. package/dist/runtime-rules/unused-component-dependencies.js.map +1 -0
  249. package/dist/runtime-rules/unused-libraries.d.ts +19 -0
  250. package/dist/runtime-rules/unused-libraries.d.ts.map +1 -0
  251. package/dist/runtime-rules/unused-libraries.js +127 -0
  252. package/dist/runtime-rules/unused-libraries.js.map +1 -0
  253. package/dist/runtime-rules/use-function-declaration.d.ts +18 -0
  254. package/dist/runtime-rules/use-function-declaration.d.ts.map +1 -0
  255. package/dist/runtime-rules/use-function-declaration.js +127 -0
  256. package/dist/runtime-rules/use-function-declaration.js.map +1 -0
  257. package/dist/runtime-rules/use-unwrap-components.d.ts +19 -0
  258. package/dist/runtime-rules/use-unwrap-components.d.ts.map +1 -0
  259. package/dist/runtime-rules/use-unwrap-components.js +84 -0
  260. package/dist/runtime-rules/use-unwrap-components.js.map +1 -0
  261. package/dist/runtime-rules/useeffect-unstable-dependencies.d.ts +23 -0
  262. package/dist/runtime-rules/useeffect-unstable-dependencies.d.ts.map +1 -0
  263. package/dist/runtime-rules/useeffect-unstable-dependencies.js +215 -0
  264. package/dist/runtime-rules/useeffect-unstable-dependencies.js.map +1 -0
  265. package/dist/runtime-rules/utilities-api-validation.d.ts +24 -0
  266. package/dist/runtime-rules/utilities-api-validation.d.ts.map +1 -0
  267. package/dist/runtime-rules/utilities-api-validation.js +121 -0
  268. package/dist/runtime-rules/utilities-api-validation.js.map +1 -0
  269. package/dist/runtime-rules/utilities-no-direct-instantiation.d.ts +20 -0
  270. package/dist/runtime-rules/utilities-no-direct-instantiation.d.ts.map +1 -0
  271. package/dist/runtime-rules/utilities-no-direct-instantiation.js +58 -0
  272. package/dist/runtime-rules/utilities-no-direct-instantiation.js.map +1 -0
  273. package/dist/runtime-rules/validate-component-references.d.ts +19 -0
  274. package/dist/runtime-rules/validate-component-references.d.ts.map +1 -0
  275. package/dist/runtime-rules/validate-component-references.js +255 -0
  276. package/dist/runtime-rules/validate-component-references.js.map +1 -0
  277. package/dist/schema-validation/component-prop-rule.d.ts +131 -0
  278. package/dist/schema-validation/component-prop-rule.d.ts.map +1 -0
  279. package/dist/schema-validation/component-prop-rule.js +625 -0
  280. package/dist/schema-validation/component-prop-rule.js.map +1 -0
  281. package/dist/schema-validation/index.d.ts +26 -0
  282. package/dist/schema-validation/index.d.ts.map +1 -0
  283. package/dist/schema-validation/index.js +26 -0
  284. package/dist/schema-validation/index.js.map +1 -0
  285. package/dist/schema-validation/semantic-validators/index.d.ts +23 -0
  286. package/dist/schema-validation/semantic-validators/index.d.ts.map +1 -0
  287. package/dist/schema-validation/semantic-validators/index.js +23 -0
  288. package/dist/schema-validation/semantic-validators/index.js.map +1 -0
  289. package/dist/schema-validation/semantic-validators/required-when-validator.d.ts +43 -0
  290. package/dist/schema-validation/semantic-validators/required-when-validator.d.ts.map +1 -0
  291. package/dist/schema-validation/semantic-validators/required-when-validator.js +94 -0
  292. package/dist/schema-validation/semantic-validators/required-when-validator.js.map +1 -0
  293. package/dist/schema-validation/semantic-validators/semantic-validator-registry.d.ts +122 -0
  294. package/dist/schema-validation/semantic-validators/semantic-validator-registry.d.ts.map +1 -0
  295. package/dist/schema-validation/semantic-validators/semantic-validator-registry.js +166 -0
  296. package/dist/schema-validation/semantic-validators/semantic-validator-registry.js.map +1 -0
  297. package/dist/schema-validation/semantic-validators/semantic-validator.d.ts +260 -0
  298. package/dist/schema-validation/semantic-validators/semantic-validator.d.ts.map +1 -0
  299. package/dist/schema-validation/semantic-validators/semantic-validator.js +301 -0
  300. package/dist/schema-validation/semantic-validators/semantic-validator.js.map +1 -0
  301. package/dist/schema-validation/semantic-validators/sql-where-clause-validator.d.ts +115 -0
  302. package/dist/schema-validation/semantic-validators/sql-where-clause-validator.d.ts.map +1 -0
  303. package/dist/schema-validation/semantic-validators/sql-where-clause-validator.js +381 -0
  304. package/dist/schema-validation/semantic-validators/sql-where-clause-validator.js.map +1 -0
  305. package/dist/schema-validation/semantic-validators/subset-of-entity-fields-validator.d.ts +60 -0
  306. package/dist/schema-validation/semantic-validators/subset-of-entity-fields-validator.d.ts.map +1 -0
  307. package/dist/schema-validation/semantic-validators/subset-of-entity-fields-validator.js +195 -0
  308. package/dist/schema-validation/semantic-validators/subset-of-entity-fields-validator.js.map +1 -0
  309. package/dist/schema-validation/semantic-validators/validation-context.d.ts +335 -0
  310. package/dist/schema-validation/semantic-validators/validation-context.d.ts.map +1 -0
  311. package/dist/schema-validation/semantic-validators/validation-context.js +13 -0
  312. package/dist/schema-validation/semantic-validators/validation-context.js.map +1 -0
  313. package/dist/styles-type-analyzer.d.ts +64 -0
  314. package/dist/styles-type-analyzer.d.ts.map +1 -0
  315. package/dist/styles-type-analyzer.js +242 -0
  316. package/dist/styles-type-analyzer.js.map +1 -0
  317. package/dist/type-context.d.ts +184 -0
  318. package/dist/type-context.d.ts.map +1 -0
  319. package/dist/type-context.js +415 -0
  320. package/dist/type-context.js.map +1 -0
  321. package/dist/type-inference-engine.d.ts +181 -0
  322. package/dist/type-inference-engine.d.ts.map +1 -0
  323. package/dist/type-inference-engine.js +1151 -0
  324. package/dist/type-inference-engine.js.map +1 -0
  325. package/dist/type-rules/type-compatibility-rule.d.ts +85 -0
  326. package/dist/type-rules/type-compatibility-rule.d.ts.map +1 -0
  327. package/dist/type-rules/type-compatibility-rule.js +211 -0
  328. package/dist/type-rules/type-compatibility-rule.js.map +1 -0
  329. package/package.json +35 -7
  330. package/README.md +0 -45
@@ -0,0 +1,1206 @@
1
+ import * as parser from '@babel/parser';
2
+ import _traverse from '@babel/traverse';
3
+ import * as t from '@babel/types';
4
+ const traverse = ((_traverse.default) ?? _traverse);
5
+ import { LibraryLintCache } from './library-lint-cache.js';
6
+ import { TypeInferenceEngine } from './type-inference-engine.js';
7
+ import { ControlFlowAnalyzer } from './control-flow-analyzer.js';
8
+ import { TypeCompatibilityRule } from './type-rules/type-compatibility-rule.js';
9
+ import { ComponentPropRule } from './schema-validation/component-prop-rule.js';
10
+ import { BaseLintRule } from './lint-rule.js';
11
+ import { MJGlobal } from '@memberjunction/global';
12
+ import { GetDialect } from '@memberjunction/sql-dialect';
13
+ // Side-effect import: triggers @RegisterClass decorators on all built-in rules
14
+ import './runtime-rules/index.js';
15
+ export class ComponentLinter {
16
+ static async validateComponentSyntax(code, componentName) {
17
+ try {
18
+ const parseResult = parser.parse(code, {
19
+ sourceType: 'module',
20
+ plugins: ['jsx', 'typescript'],
21
+ errorRecovery: true,
22
+ ranges: true,
23
+ });
24
+ if (parseResult.errors && parseResult.errors.length > 0) {
25
+ const errors = parseResult.errors.map((error) => {
26
+ const location = error.loc ? `Line ${error.loc.line}, Column ${error.loc.column}` : 'Unknown location';
27
+ return `${location}: ${error.message || error.toString()}`;
28
+ });
29
+ return {
30
+ valid: false,
31
+ errors,
32
+ };
33
+ }
34
+ return {
35
+ valid: true,
36
+ errors: [],
37
+ };
38
+ }
39
+ catch (error) {
40
+ // Handle catastrophic parse failures
41
+ const errorMessage = error instanceof Error ? error.message : 'Unknown parsing error';
42
+ return {
43
+ valid: false,
44
+ errors: [`Failed to parse component: ${errorMessage}`],
45
+ };
46
+ }
47
+ }
48
+ /**
49
+ * The SQL dialect used for WHERE clause validation in semantic validators.
50
+ * Defaults to SQL Server. Set via the `sqlDialect` parameter on `lintComponent()`.
51
+ */
52
+ static { this._sqlDialect = GetDialect('sqlserver'); }
53
+ /** Current SQL dialect used for WHERE clause parsing */
54
+ static get SqlDialect() {
55
+ return ComponentLinter._sqlDialect;
56
+ }
57
+ static async lintComponent(code, componentName, componentSpec, isRootComponent, contextUser, debugMode, options, sqlDialect) {
58
+ if (sqlDialect) {
59
+ ComponentLinter._sqlDialect = sqlDialect;
60
+ }
61
+ try {
62
+ // Require contextUser when libraries need to be checked
63
+ if (componentSpec?.libraries && componentSpec.libraries.length > 0 && !contextUser) {
64
+ throw new Error('contextUser is required when linting components with library dependencies. This is needed to load library-specific lint rules from the database.');
65
+ }
66
+ // Parse with error recovery to get both AST and errors
67
+ const parseResult = parser.parse(code, {
68
+ sourceType: 'module',
69
+ plugins: ['jsx', 'typescript'],
70
+ errorRecovery: true,
71
+ attachComment: false,
72
+ ranges: true,
73
+ tokens: false,
74
+ });
75
+ // Check for syntax errors from parser
76
+ const syntaxViolations = [];
77
+ if (parseResult.errors && parseResult.errors.length > 0) {
78
+ for (const error of parseResult.errors) {
79
+ const err = error; // Babel parser errors don't have proper types
80
+ syntaxViolations.push({
81
+ rule: 'syntax-error',
82
+ severity: 'critical',
83
+ line: err.loc?.line || 0,
84
+ column: err.loc?.column || 0,
85
+ message: `Syntax error in component "${componentName}": ${err.message || err.toString()}`,
86
+ code: err.code || 'BABEL_PARSER_ERROR',
87
+ });
88
+ }
89
+ }
90
+ // If we have critical syntax errors, return immediately with those
91
+ if (syntaxViolations.length > 0) {
92
+ // Add suggestions directly to syntax violations
93
+ this.generateSyntaxErrorSuggestions(syntaxViolations);
94
+ return {
95
+ success: false,
96
+ violations: syntaxViolations,
97
+ criticalCount: syntaxViolations.length,
98
+ highCount: 0,
99
+ mediumCount: 0,
100
+ lowCount: 0,
101
+ hasErrors: true,
102
+ };
103
+ }
104
+ // Continue with existing linting logic
105
+ const ast = parseResult;
106
+ // ═══════════════════════════════════════════════════════════════════════════
107
+ // PHASE 1 REFACTOR: Run Type Inference ONCE before all rules
108
+ // This creates a shared TypeContext that all rules can consume
109
+ // ═══════════════════════════════════════════════════════════════════════════
110
+ const typeEngine = new TypeInferenceEngine(componentSpec, contextUser);
111
+ const controlFlowAnalyzer = new ControlFlowAnalyzer(ast, componentSpec);
112
+ // Run type inference analysis once
113
+ await typeEngine.analyze(ast);
114
+ const typeContext = typeEngine.getTypeContext();
115
+ // Discover all registered lint rules via ClassFactory
116
+ const ruleRegistrations = MJGlobal.Instance.ClassFactory.GetAllRegistrations(BaseLintRule);
117
+ const allRules = [];
118
+ for (const reg of ruleRegistrations) {
119
+ if (!reg.Key)
120
+ continue; // Skip the base class registration (no key)
121
+ const instance = MJGlobal.Instance.ClassFactory.CreateInstance(BaseLintRule, reg.Key);
122
+ if (instance)
123
+ allRules.push(instance);
124
+ }
125
+ // Filter rules based on component type
126
+ const applicableRules = isRootComponent
127
+ ? allRules.filter(rule => rule.AppliesTo === 'all' || rule.AppliesTo === 'root')
128
+ : allRules.filter(rule => rule.AppliesTo === 'all' || rule.AppliesTo === 'child');
129
+ const violations = [];
130
+ // Run each rule with error handling to prevent crashes
131
+ for (const rule of applicableRules) {
132
+ try {
133
+ const ruleViolations = rule.Test(ast, componentName, componentSpec, options, typeContext);
134
+ violations.push(...ruleViolations);
135
+ }
136
+ catch (error) {
137
+ console.warn(`Rule "${rule.Name}" failed during execution:`, error instanceof Error ? error.message : error);
138
+ if (debugMode) {
139
+ console.error('Full error:', error);
140
+ }
141
+ }
142
+ }
143
+ // ═══════════════════════════════════════════════════════════════════════════
144
+ // PHASE 1 REFACTOR: Run new TypeCompatibilityRule with shared context
145
+ // This consolidates all type checking into a single rule
146
+ // ═══════════════════════════════════════════════════════════════════════════
147
+ try {
148
+ const typeCompatRule = new TypeCompatibilityRule();
149
+ const lintContext = {
150
+ componentName,
151
+ componentSpec,
152
+ typeContext,
153
+ typeEngine,
154
+ controlFlowAnalyzer,
155
+ sqlDialect: ComponentLinter._sqlDialect,
156
+ };
157
+ const typeViolations = typeCompatRule.validate(ast, lintContext);
158
+ violations.push(...typeViolations);
159
+ }
160
+ catch (error) {
161
+ console.warn('TypeCompatibilityRule failed during execution:', error instanceof Error ? error.message : error);
162
+ if (debugMode) {
163
+ console.error('Full error:', error);
164
+ }
165
+ }
166
+ // ═══════════════════════════════════════════════════════════════════════════
167
+ // PHASE 3 REFACTOR: Run new ComponentPropRule with shared context
168
+ // This consolidates all component prop validation into a single rule:
169
+ // - Prop existence (from dependency-prop-validation)
170
+ // - Required props checking
171
+ // - Prop type validation (via TypeContext)
172
+ // - Semantic constraint validation (via SemanticValidators)
173
+ // - Unknown props warning
174
+ // ═══════════════════════════════════════════════════════════════════════════
175
+ try {
176
+ const componentPropRule = new ComponentPropRule();
177
+ const lintContext = {
178
+ componentName,
179
+ componentSpec,
180
+ typeContext,
181
+ typeEngine,
182
+ controlFlowAnalyzer,
183
+ sqlDialect: ComponentLinter._sqlDialect,
184
+ componentResolver: options?.componentResolver,
185
+ };
186
+ const propViolations = componentPropRule.validate(ast, lintContext);
187
+ violations.push(...propViolations);
188
+ }
189
+ catch (error) {
190
+ console.warn('ComponentPropRule failed during execution:', error instanceof Error ? error.message : error);
191
+ if (debugMode) {
192
+ console.error('Full error:', error);
193
+ }
194
+ }
195
+ // Add data requirements validation if componentSpec is provided
196
+ if (componentSpec?.dataRequirements?.entities) {
197
+ try {
198
+ const dataViolations = this.validateDataRequirements(ast, componentSpec, options);
199
+ violations.push(...dataViolations);
200
+ }
201
+ catch (error) {
202
+ console.warn('Data requirements validation failed:', error instanceof Error ? error.message : error);
203
+ if (debugMode) {
204
+ console.error('Full error:', error);
205
+ }
206
+ }
207
+ }
208
+ // Apply library-specific lint rules if available
209
+ if (componentSpec?.libraries) {
210
+ const libraryViolations = await this.applyLibraryLintRules(ast, componentSpec, contextUser, debugMode);
211
+ violations.push(...libraryViolations);
212
+ }
213
+ // Deduplicate violations - keep only unique rule+message combinations
214
+ const uniqueViolations = this.deduplicateViolations(violations);
215
+ // Count violations by severity
216
+ const criticalCount = uniqueViolations.filter((v) => v.severity === 'critical').length;
217
+ const highCount = uniqueViolations.filter((v) => v.severity === 'high').length;
218
+ const mediumCount = uniqueViolations.filter((v) => v.severity === 'medium').length;
219
+ const lowCount = uniqueViolations.filter((v) => v.severity === 'low').length;
220
+ // Debug mode summary
221
+ if (debugMode && uniqueViolations.length > 0) {
222
+ console.log('\n' + '='.repeat(60));
223
+ console.log('📊 LINT SUMMARY:');
224
+ console.log('='.repeat(60));
225
+ if (criticalCount > 0)
226
+ console.log(` 🔴 Critical: ${criticalCount}`);
227
+ if (highCount > 0)
228
+ console.log(` 🟠 High: ${highCount}`);
229
+ if (mediumCount > 0)
230
+ console.log(` 🟡 Medium: ${mediumCount}`);
231
+ if (lowCount > 0)
232
+ console.log(` 🟢 Low: ${lowCount}`);
233
+ console.log('='.repeat(60));
234
+ // Group violations by library
235
+ const libraryViolations = uniqueViolations.filter((v) => v.rule.includes('-validator'));
236
+ if (libraryViolations.length > 0) {
237
+ console.log('\n📚 Library-Specific Issues:');
238
+ const byLibrary = new Map();
239
+ libraryViolations.forEach((v) => {
240
+ const lib = v.rule.replace('-validator', '');
241
+ if (!byLibrary.has(lib))
242
+ byLibrary.set(lib, []);
243
+ byLibrary.get(lib).push(v);
244
+ });
245
+ byLibrary.forEach((violations, library) => {
246
+ console.log(` • ${library}: ${violations.length} issue${violations.length > 1 ? 's' : ''}`);
247
+ });
248
+ }
249
+ console.log('');
250
+ }
251
+ return {
252
+ success: criticalCount === 0 && highCount === 0, // Only fail on critical/high
253
+ violations: uniqueViolations,
254
+ criticalCount,
255
+ highCount,
256
+ mediumCount,
257
+ lowCount,
258
+ hasErrors: criticalCount > 0 || highCount > 0,
259
+ };
260
+ }
261
+ catch (error) {
262
+ // If parsing fails, return a parse error
263
+ // Log stack trace for debugging
264
+ if (error instanceof Error && error.stack) {
265
+ console.error('Parse error stack trace:', error.stack);
266
+ }
267
+ return {
268
+ success: false,
269
+ violations: [
270
+ {
271
+ rule: 'parse-error',
272
+ severity: 'critical',
273
+ line: 0,
274
+ column: 0,
275
+ message: `Failed to parse component: ${error instanceof Error ? error.message : 'Unknown error'}`,
276
+ },
277
+ ],
278
+ hasErrors: true,
279
+ };
280
+ }
281
+ }
282
+ static validateDataRequirements(ast, componentSpec, options) {
283
+ const violations = [];
284
+ // Extract entity names from dataRequirements
285
+ const requiredEntities = new Set();
286
+ const requiredQueries = new Set();
287
+ // Map to store full query definitions for parameter validation
288
+ const queryDefinitionsMap = new Map();
289
+ // Map to track allowed fields per entity (from dataRequirements display/filter/sort arrays)
290
+ const entityFieldsMap = new Map();
291
+ // Map to track ALL fields that exist in the entity
292
+ // Used to distinguish "field not in requirements" (medium) from "field doesn't exist" (critical)
293
+ const entityAllFieldsMap = new Map();
294
+ // FIRST: Populate entityAllFieldsMap from options.entityMetadata if provided
295
+ // This gives us the complete list of fields that actually exist in each entity
296
+ if (options?.entityMetadata && Array.isArray(options.entityMetadata)) {
297
+ for (const entity of options.entityMetadata) {
298
+ if (entity.name && entity.fields) {
299
+ const fieldNames = new Set(entity.fields.map((f) => f.name));
300
+ entityAllFieldsMap.set(entity.name, fieldNames);
301
+ }
302
+ }
303
+ }
304
+ if (componentSpec.dataRequirements?.entities) {
305
+ for (const entity of componentSpec.dataRequirements.entities) {
306
+ if (entity.name) {
307
+ requiredEntities.add(entity.name);
308
+ entityFieldsMap.set(entity.name, {
309
+ displayFields: new Set(entity.displayFields || []),
310
+ filterFields: new Set(entity.filterFields || []),
311
+ sortFields: new Set(entity.sortFields || []),
312
+ });
313
+ // Build set of ALL fields from fieldMetadata if available
314
+ // Only use fieldMetadata as fallback if entityMetadata wasn't provided for this entity
315
+ if (!entityAllFieldsMap.has(entity.name) && entity.fieldMetadata && Array.isArray(entity.fieldMetadata)) {
316
+ const allFields = new Set();
317
+ for (const field of entity.fieldMetadata) {
318
+ if (field.name) {
319
+ allFields.add(field.name);
320
+ }
321
+ }
322
+ entityAllFieldsMap.set(entity.name, allFields);
323
+ }
324
+ }
325
+ }
326
+ }
327
+ if (componentSpec.dataRequirements?.queries) {
328
+ for (const query of componentSpec.dataRequirements.queries) {
329
+ if (query.name) {
330
+ requiredQueries.add(query.name);
331
+ queryDefinitionsMap.set(query.name, query);
332
+ }
333
+ }
334
+ }
335
+ // Also check child components' dataRequirements
336
+ if (componentSpec.dependencies) {
337
+ for (const dep of componentSpec.dependencies) {
338
+ if (dep.dataRequirements?.entities) {
339
+ for (const entity of dep.dataRequirements.entities) {
340
+ if (entity.name) {
341
+ requiredEntities.add(entity.name);
342
+ // Merge fields if entity already exists
343
+ const existing = entityFieldsMap.get(entity.name);
344
+ if (existing) {
345
+ (entity.displayFields || []).forEach((f) => existing.displayFields.add(f));
346
+ (entity.filterFields || []).forEach((f) => existing.filterFields.add(f));
347
+ (entity.sortFields || []).forEach((f) => existing.sortFields.add(f));
348
+ }
349
+ else {
350
+ entityFieldsMap.set(entity.name, {
351
+ displayFields: new Set(entity.displayFields || []),
352
+ filterFields: new Set(entity.filterFields || []),
353
+ sortFields: new Set(entity.sortFields || []),
354
+ });
355
+ }
356
+ // Merge fieldMetadata into allFields map only if entityMetadata wasn't provided
357
+ // If entityMetadata was provided, it already has the complete field list
358
+ if (!entityAllFieldsMap.has(entity.name) && entity.fieldMetadata && Array.isArray(entity.fieldMetadata)) {
359
+ const existingAll = new Set();
360
+ for (const field of entity.fieldMetadata) {
361
+ if (field.name) {
362
+ existingAll.add(field.name);
363
+ }
364
+ }
365
+ entityAllFieldsMap.set(entity.name, existingAll);
366
+ }
367
+ }
368
+ }
369
+ }
370
+ if (dep.dataRequirements?.queries) {
371
+ for (const query of dep.dataRequirements.queries) {
372
+ if (query.name) {
373
+ requiredQueries.add(query.name);
374
+ queryDefinitionsMap.set(query.name, query);
375
+ }
376
+ }
377
+ }
378
+ }
379
+ }
380
+ // Find all RunView, RunViews, and RunQuery calls in the code
381
+ traverse(ast, {
382
+ CallExpression(path) {
383
+ // Check for utilities.rv.RunView or utilities.rv.RunViews pattern
384
+ if (t.isMemberExpression(path.node.callee) &&
385
+ t.isMemberExpression(path.node.callee.object) &&
386
+ t.isIdentifier(path.node.callee.object.object) &&
387
+ path.node.callee.object.object.name === 'utilities' &&
388
+ t.isIdentifier(path.node.callee.object.property) &&
389
+ path.node.callee.object.property.name === 'rv' &&
390
+ t.isIdentifier(path.node.callee.property) &&
391
+ (path.node.callee.property.name === 'RunView' || path.node.callee.property.name === 'RunViews')) {
392
+ // For RunViews, it might be an array of configs
393
+ const configs = path.node.callee.property.name === 'RunViews' && path.node.arguments.length > 0 && t.isArrayExpression(path.node.arguments[0])
394
+ ? path.node.arguments[0].elements.filter((e) => t.isObjectExpression(e))
395
+ : path.node.arguments.length > 0 && t.isObjectExpression(path.node.arguments[0])
396
+ ? [path.node.arguments[0]]
397
+ : [];
398
+ // Check each config object
399
+ for (const configObj of configs) {
400
+ if (t.isObjectExpression(configObj)) {
401
+ // Find EntityName property
402
+ for (const prop of configObj.properties) {
403
+ if (t.isObjectProperty(prop) && t.isIdentifier(prop.key) && prop.key.name === 'EntityName' && t.isStringLiteral(prop.value)) {
404
+ const usedEntity = prop.value.value;
405
+ // Check if this entity is in the required entities
406
+ if (requiredEntities.size > 0 && !requiredEntities.has(usedEntity)) {
407
+ // Enhanced fuzzy matching for better suggestions
408
+ const possibleMatches = Array.from(requiredEntities).filter((e) => {
409
+ const eLower = e.toLowerCase();
410
+ const usedLower = usedEntity.toLowerCase();
411
+ // Check various matching patterns
412
+ return (
413
+ // Contains match
414
+ eLower.includes(usedLower) ||
415
+ usedLower.includes(eLower) ||
416
+ // Remove spaces and check
417
+ eLower.replace(/\s+/g, '').includes(usedLower.replace(/\s+/g, '')) ||
418
+ usedLower.replace(/\s+/g, '').includes(eLower.replace(/\s+/g, '')) ||
419
+ // Check if the main words match (ignore prefixes like "MJ:")
420
+ eLower.replace(/^mj:\s*/i, '').includes(usedLower) ||
421
+ usedLower.includes(eLower.replace(/^mj:\s*/i, '')));
422
+ });
423
+ // Always show all available entities for clarity
424
+ const allEntities = Array.from(requiredEntities);
425
+ const entityList = allEntities.length <= 5 ? allEntities.join(', ') : allEntities.slice(0, 5).join(', ') + `, ... (${allEntities.length} total)`;
426
+ let message = `Entity "${usedEntity}" not found in dataRequirements.`;
427
+ if (possibleMatches.length > 0) {
428
+ message += ` Did you mean "${possibleMatches[0]}"?`;
429
+ }
430
+ message += ` Available entities: ${entityList}`;
431
+ violations.push({
432
+ rule: 'entity-name-mismatch',
433
+ severity: 'critical',
434
+ line: prop.value.loc?.start.line || 0,
435
+ column: prop.value.loc?.start.column || 0,
436
+ message,
437
+ code: `EntityName: "${usedEntity}"`,
438
+ });
439
+ }
440
+ else {
441
+ // Entity is valid, now check fields
442
+ const entityFields = entityFieldsMap.get(usedEntity);
443
+ if (entityFields) {
444
+ // Check Fields array
445
+ const fieldsProperty = configObj.properties.find((p) => t.isObjectProperty(p) && t.isIdentifier(p.key) && p.key.name === 'Fields');
446
+ if (fieldsProperty && t.isObjectProperty(fieldsProperty) && t.isArrayExpression(fieldsProperty.value)) {
447
+ for (const fieldElement of fieldsProperty.value.elements) {
448
+ if (t.isStringLiteral(fieldElement)) {
449
+ const fieldName = fieldElement.value;
450
+ // Check for SQL functions
451
+ if (/COUNT\s*\(|SUM\s*\(|AVG\s*\(|MAX\s*\(|MIN\s*\(/i.test(fieldName)) {
452
+ violations.push({
453
+ rule: 'runview-sql-function',
454
+ severity: 'critical',
455
+ line: fieldElement.loc?.start.line || 0,
456
+ column: fieldElement.loc?.start.column || 0,
457
+ message: `RunView does not support SQL aggregations. Use RunQuery for aggregations or fetch raw data and aggregate in JavaScript.`,
458
+ code: fieldName,
459
+ });
460
+ }
461
+ else {
462
+ // Check if field is in allowed fields
463
+ const isAllowed = entityFields.displayFields.has(fieldName) || entityFields.filterFields.has(fieldName) || entityFields.sortFields.has(fieldName);
464
+ if (!isAllowed) {
465
+ // Check if field exists in entity metadata (two-tier severity)
466
+ const allFields = entityAllFieldsMap.get(usedEntity);
467
+ const existsInEntity = allFields ? allFields.has(fieldName) : false;
468
+ if (existsInEntity) {
469
+ // Field exists but not in dataRequirements - medium severity (works but suboptimal)
470
+ violations.push({
471
+ rule: 'field-not-in-requirements',
472
+ severity: 'medium',
473
+ line: fieldElement.loc?.start.line || 0,
474
+ column: fieldElement.loc?.start.column || 0,
475
+ message: `Field "${fieldName}" exists in entity "${usedEntity}" but not declared in dataRequirements. Consider adding to displayFields, filterFields, or sortFields.`,
476
+ code: fieldName,
477
+ });
478
+ }
479
+ else {
480
+ // Field doesn't exist in entity - critical severity (will fail at runtime)
481
+ violations.push({
482
+ rule: 'field-not-in-requirements',
483
+ severity: 'critical',
484
+ line: fieldElement.loc?.start.line || 0,
485
+ column: fieldElement.loc?.start.column || 0,
486
+ message: `Field "${fieldName}" does not exist in entity "${usedEntity}". Available fields: ${[
487
+ ...entityFields.displayFields,
488
+ ...entityFields.filterFields,
489
+ ...entityFields.sortFields,
490
+ ].join(', ')}`,
491
+ code: fieldName,
492
+ });
493
+ }
494
+ }
495
+ }
496
+ }
497
+ }
498
+ }
499
+ // Check OrderBy field
500
+ const orderByProperty = configObj.properties.find((p) => t.isObjectProperty(p) && t.isIdentifier(p.key) && p.key.name === 'OrderBy');
501
+ if (orderByProperty && t.isObjectProperty(orderByProperty) && t.isStringLiteral(orderByProperty.value)) {
502
+ const orderByValue = orderByProperty.value.value;
503
+ // Extract field name from OrderBy (e.g., "AccountName ASC" -> "AccountName")
504
+ const orderByField = orderByValue.split(/\s+/)[0];
505
+ if (!entityFields.sortFields.has(orderByField)) {
506
+ // Check if field exists in entity metadata (two-tier severity)
507
+ const allFields = entityAllFieldsMap.get(usedEntity);
508
+ const existsInEntity = allFields ? allFields.has(orderByField) : false;
509
+ if (existsInEntity) {
510
+ // Field exists but not in sortFields - medium severity (works but suboptimal)
511
+ violations.push({
512
+ rule: 'orderby-field-not-sortable',
513
+ severity: 'medium',
514
+ line: orderByProperty.value.loc?.start.line || 0,
515
+ column: orderByProperty.value.loc?.start.column || 0,
516
+ message: `OrderBy field "${orderByField}" exists in entity "${usedEntity}" but not declared in sortFields. Consider adding for optimization.`,
517
+ code: orderByValue,
518
+ });
519
+ }
520
+ else {
521
+ // Field doesn't exist in entity - critical severity (will fail at runtime)
522
+ violations.push({
523
+ rule: 'orderby-field-not-sortable',
524
+ severity: 'critical',
525
+ line: orderByProperty.value.loc?.start.line || 0,
526
+ column: orderByProperty.value.loc?.start.column || 0,
527
+ message: `OrderBy field "${orderByField}" does not exist in entity "${usedEntity}". Available sort fields: ${[
528
+ ...entityFields.sortFields,
529
+ ].join(', ')}`,
530
+ code: orderByValue,
531
+ });
532
+ }
533
+ }
534
+ }
535
+ }
536
+ }
537
+ }
538
+ }
539
+ }
540
+ }
541
+ }
542
+ // Check for utilities.rv.RunQuery pattern
543
+ if (t.isMemberExpression(path.node.callee) &&
544
+ t.isMemberExpression(path.node.callee.object) &&
545
+ t.isIdentifier(path.node.callee.object.object) &&
546
+ path.node.callee.object.object.name === 'utilities' &&
547
+ t.isIdentifier(path.node.callee.object.property) &&
548
+ path.node.callee.object.property.name === 'rv' &&
549
+ t.isIdentifier(path.node.callee.property) &&
550
+ path.node.callee.property.name === 'RunQuery') {
551
+ // Check the first argument (should be an object with QueryName)
552
+ if (path.node.arguments.length > 0 && t.isObjectExpression(path.node.arguments[0])) {
553
+ const configObj = path.node.arguments[0];
554
+ // Find QueryName property
555
+ for (const prop of configObj.properties) {
556
+ if (t.isObjectProperty(prop) && t.isIdentifier(prop.key) && prop.key.name === 'QueryName' && t.isStringLiteral(prop.value)) {
557
+ const usedQuery = prop.value.value;
558
+ // Check if this query is in the required queries
559
+ if (requiredQueries.size > 0 && !requiredQueries.has(usedQuery)) {
560
+ // Enhanced fuzzy matching for better suggestions
561
+ const possibleMatches = Array.from(requiredQueries).filter((q) => {
562
+ const qLower = q.toLowerCase();
563
+ const usedLower = usedQuery.toLowerCase();
564
+ return (
565
+ // Contains match
566
+ qLower.includes(usedLower) ||
567
+ usedLower.includes(qLower) ||
568
+ // Remove spaces and check
569
+ qLower.replace(/\s+/g, '').includes(usedLower.replace(/\s+/g, '')) ||
570
+ usedLower.replace(/\s+/g, '').includes(qLower.replace(/\s+/g, '')));
571
+ });
572
+ // Always show all available queries for clarity
573
+ const allQueries = Array.from(requiredQueries);
574
+ const queryList = allQueries.length <= 5 ? allQueries.join(', ') : allQueries.slice(0, 5).join(', ') + `, ... (${allQueries.length} total)`;
575
+ let message = `Query "${usedQuery}" not found in dataRequirements.`;
576
+ if (possibleMatches.length > 0) {
577
+ message += ` Did you mean "${possibleMatches[0]}"?`;
578
+ }
579
+ if (requiredQueries.size > 0) {
580
+ message += ` Available queries: ${queryList}`;
581
+ }
582
+ else {
583
+ message += ` No queries defined in dataRequirements.`;
584
+ }
585
+ violations.push({
586
+ rule: 'query-name-mismatch',
587
+ severity: 'critical',
588
+ line: prop.value.loc?.start.line || 0,
589
+ column: prop.value.loc?.start.column || 0,
590
+ message,
591
+ code: `QueryName: "${usedQuery}"`,
592
+ });
593
+ }
594
+ else if (queryDefinitionsMap.has(usedQuery)) {
595
+ // Query is valid, now check parameters
596
+ const queryDef = queryDefinitionsMap.get(usedQuery);
597
+ if (queryDef?.parameters && queryDef.parameters.length > 0) {
598
+ // Extract parameters from the RunQuery call
599
+ const paramsInCall = new Map();
600
+ // Look for Parameters property in the config object
601
+ for (const prop of configObj.properties) {
602
+ if (t.isObjectProperty(prop) && t.isIdentifier(prop.key) && prop.key.name === 'Parameters' && t.isObjectExpression(prop.value)) {
603
+ // Extract each parameter from the Parameters object
604
+ for (const paramProp of prop.value.properties) {
605
+ if (t.isObjectProperty(paramProp) && t.isIdentifier(paramProp.key)) {
606
+ paramsInCall.set(paramProp.key.name, paramProp);
607
+ }
608
+ }
609
+ // Check for required parameters
610
+ const requiredParams = queryDef.parameters.filter((p) => p.value !== '@runtime' || p.value === '@runtime');
611
+ for (const reqParam of requiredParams) {
612
+ if (!paramsInCall.has(reqParam.name)) {
613
+ violations.push({
614
+ rule: 'missing-query-parameter',
615
+ severity: 'critical',
616
+ line: prop.value.loc?.start.line || 0,
617
+ column: prop.value.loc?.start.column || 0,
618
+ message: `Missing required parameter "${reqParam.name}" for query "${usedQuery}". ${reqParam.description ? `Description: ${reqParam.description}` : ''}`,
619
+ code: `Parameters: { ${reqParam.name}: ... }`,
620
+ });
621
+ }
622
+ }
623
+ // Check for unknown parameters
624
+ const validParamNames = new Set(queryDef.parameters.map((p) => p.name));
625
+ for (const [paramName, paramNode] of paramsInCall) {
626
+ if (!validParamNames.has(paramName)) {
627
+ violations.push({
628
+ rule: 'unknown-query-parameter',
629
+ severity: 'high',
630
+ line: paramNode.loc?.start.line || 0,
631
+ column: paramNode.loc?.start.column || 0,
632
+ message: `Unknown parameter "${paramName}" for query "${usedQuery}". Valid parameters: ${Array.from(validParamNames).join(', ')}`,
633
+ code: `${paramName}: ...`,
634
+ });
635
+ }
636
+ }
637
+ break; // Found Parameters property, no need to continue
638
+ }
639
+ }
640
+ // If query has parameters but no Parameters property was found in the call
641
+ if (paramsInCall.size === 0 && queryDef?.parameters && queryDef.parameters.length > 0) {
642
+ violations.push({
643
+ rule: 'missing-parameters-object',
644
+ severity: 'critical',
645
+ line: configObj.loc?.start.line || 0,
646
+ column: configObj.loc?.start.column || 0,
647
+ message: `Query "${usedQuery}" requires parameters but none were provided. Required parameters: ${queryDef.parameters.map((p) => p.name).join(', ')}`,
648
+ code: `RunQuery({ QueryName: "${usedQuery}", Parameters: { ... } })`,
649
+ });
650
+ }
651
+ }
652
+ }
653
+ }
654
+ }
655
+ }
656
+ }
657
+ },
658
+ });
659
+ return violations;
660
+ }
661
+ static deduplicateViolations(violations) {
662
+ const seen = new Set();
663
+ const unique = [];
664
+ for (const violation of violations) {
665
+ // Create a key from the complete violation details (case-insensitive for message)
666
+ const key = `${violation.rule}:${violation.severity}:${violation.line}:${violation.column}:${violation.message.toLowerCase()}`;
667
+ if (!seen.has(key)) {
668
+ seen.add(key);
669
+ unique.push(violation);
670
+ }
671
+ }
672
+ // Sort by severity (critical > high > medium > low) and then by line number
673
+ const severityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
674
+ unique.sort((a, b) => {
675
+ const severityDiff = severityOrder[a.severity] - severityOrder[b.severity];
676
+ if (severityDiff !== 0)
677
+ return severityDiff;
678
+ return a.line - b.line;
679
+ });
680
+ return unique;
681
+ }
682
+ static generateSyntaxErrorSuggestions(violations) {
683
+ for (const violation of violations) {
684
+ if (violation.message.includes('Unterminated string')) {
685
+ violation.suggestion = {
686
+ text: 'Check that all string literals are properly closed with matching quotes',
687
+ example: 'Template literals with interpolation must use backticks: `text ${variable} text`',
688
+ };
689
+ }
690
+ else if (violation.message.includes('Unexpected token') || violation.message.includes('export')) {
691
+ violation.suggestion = {
692
+ text: 'Ensure all code is within the component function body',
693
+ example: 'Remove any export statements or code outside the function definition',
694
+ };
695
+ }
696
+ else if (violation.message.includes('import') && violation.message.includes('top level')) {
697
+ violation.suggestion = {
698
+ text: 'Import statements are not allowed in components - use props instead',
699
+ example: 'Access libraries through props: const { React, MaterialUI } = props.components',
700
+ };
701
+ }
702
+ else {
703
+ violation.suggestion = {
704
+ text: 'Fix the syntax error before the component can be compiled',
705
+ example: 'Review the code at the specified line and column for syntax issues',
706
+ };
707
+ }
708
+ }
709
+ }
710
+ /**
711
+ * Apply library-specific lint rules based on ComponentLibrary LintRules field
712
+ */
713
+ static async applyLibraryLintRules(ast, componentSpec, contextUser, debugMode) {
714
+ const violations = [];
715
+ try {
716
+ // Use the cached and compiled library rules
717
+ const cache = LibraryLintCache.getInstance();
718
+ await cache.loadLibraryRules(contextUser);
719
+ // Check each library that this component uses
720
+ if (componentSpec.libraries) {
721
+ // Run library checks in parallel for performance
722
+ const libraryPromises = componentSpec.libraries.map(async (lib) => {
723
+ const libraryViolations = [];
724
+ // Get the cached and compiled rules for this library
725
+ const compiledRules = cache.getLibraryRules(lib.name);
726
+ if (debugMode) {
727
+ console.log(`\n 📚 Library: ${lib.name}`);
728
+ if (compiledRules) {
729
+ console.log(` ┌─ Has lint rules: ✅`);
730
+ if (compiledRules.validators) {
731
+ console.log(` ├─ Validators: ${Object.keys(compiledRules.validators).length}`);
732
+ }
733
+ if (compiledRules.initialization) {
734
+ console.log(` ├─ Initialization rules: ✅`);
735
+ }
736
+ if (compiledRules.lifecycle) {
737
+ console.log(` ├─ Lifecycle rules: ✅`);
738
+ }
739
+ console.log(` └─ Starting checks...`);
740
+ }
741
+ else {
742
+ console.log(` └─ No lint rules defined`);
743
+ }
744
+ }
745
+ if (compiledRules) {
746
+ const library = compiledRules.library;
747
+ const libraryName = library.Name || lib.name;
748
+ // Apply initialization rules
749
+ if (compiledRules.initialization) {
750
+ if (debugMode) {
751
+ console.log(` ├─ 🔍 Checking ${libraryName} initialization patterns...`);
752
+ }
753
+ const initViolations = this.checkLibraryInitialization(ast, libraryName, compiledRules.initialization);
754
+ // Debug logging for library violations
755
+ if (debugMode && initViolations.length > 0) {
756
+ console.log(` │ ⚠️ Found ${initViolations.length} initialization issue${initViolations.length > 1 ? 's' : ''}`);
757
+ initViolations.forEach((v) => {
758
+ const icon = v.severity === 'critical' ? '🔴' : v.severity === 'high' ? '🟠' : v.severity === 'medium' ? '🟡' : '🟢';
759
+ console.log(` │ ${icon} Line ${v.line}: ${v.message}`);
760
+ });
761
+ }
762
+ libraryViolations.push(...initViolations);
763
+ }
764
+ // Apply lifecycle rules
765
+ if (compiledRules.lifecycle) {
766
+ if (debugMode) {
767
+ console.log(` ├─ 🔄 Checking ${libraryName} lifecycle management...`);
768
+ }
769
+ const lifecycleViolations = this.checkLibraryLifecycle(ast, libraryName, compiledRules.lifecycle);
770
+ // Debug logging for library violations
771
+ if (debugMode && lifecycleViolations.length > 0) {
772
+ console.log(` │ ⚠️ Found ${lifecycleViolations.length} lifecycle issue${lifecycleViolations.length > 1 ? 's' : ''}`);
773
+ lifecycleViolations.forEach((v) => {
774
+ const icon = v.severity === 'critical' ? '🔴' : v.severity === 'high' ? '🟠' : v.severity === 'medium' ? '🟡' : '🟢';
775
+ console.log(` │ ${icon} Line ${v.line}: ${v.message}`);
776
+ });
777
+ }
778
+ libraryViolations.push(...lifecycleViolations);
779
+ }
780
+ // Apply options validation
781
+ if (compiledRules.options) {
782
+ if (debugMode) {
783
+ console.log(` ├─ ⚙️ Checking ${libraryName} configuration options...`);
784
+ }
785
+ const optionsViolations = this.checkLibraryOptions(ast, libraryName, compiledRules.options);
786
+ // Debug logging for library violations
787
+ if (debugMode && optionsViolations.length > 0) {
788
+ console.log(` │ ⚠️ Found ${optionsViolations.length} configuration issue${optionsViolations.length > 1 ? 's' : ''}`);
789
+ optionsViolations.forEach((v) => {
790
+ const icon = v.severity === 'critical' ? '🔴' : v.severity === 'high' ? '🟠' : v.severity === 'medium' ? '🟡' : '🟢';
791
+ console.log(` │ ${icon} Line ${v.line}: ${v.message}`);
792
+ });
793
+ }
794
+ libraryViolations.push(...optionsViolations);
795
+ }
796
+ // Apply compiled validators (already compiled in cache)
797
+ if (compiledRules.validators) {
798
+ const validatorViolations = this.executeCompiledValidators(ast, libraryName, library.GlobalVariable || '', compiledRules.validators, debugMode);
799
+ libraryViolations.push(...validatorViolations);
800
+ }
801
+ }
802
+ return libraryViolations;
803
+ });
804
+ // Wait for all library checks to complete
805
+ const allLibraryViolations = await Promise.all(libraryPromises);
806
+ // Flatten the results
807
+ allLibraryViolations.forEach((libViolations) => {
808
+ violations.push(...libViolations);
809
+ });
810
+ }
811
+ }
812
+ catch (error) {
813
+ console.warn('Failed to apply library lint rules:', error);
814
+ }
815
+ return violations;
816
+ }
817
+ /**
818
+ * Check library initialization patterns (constructor, element type, etc.)
819
+ */
820
+ static checkLibraryInitialization(ast, libraryName, rules) {
821
+ const violations = [];
822
+ traverse(ast, {
823
+ // Check for new ConstructorName() patterns
824
+ NewExpression(path) {
825
+ if (t.isIdentifier(path.node.callee) && path.node.callee.name === rules.constructorName) {
826
+ // Check if it requires 'new' keyword
827
+ if (rules.requiresNew === false) {
828
+ violations.push({
829
+ rule: 'library-initialization',
830
+ severity: 'critical',
831
+ line: path.node.loc?.start.line || 0,
832
+ column: path.node.loc?.start.column || 0,
833
+ message: `${libraryName}: ${rules.constructorName} should not use 'new' keyword`,
834
+ code: `${rules.constructorName}(...) // without new`,
835
+ });
836
+ }
837
+ // Check element type if first argument is a ref
838
+ if (rules.elementType && path.node.arguments[0]) {
839
+ const firstArg = path.node.arguments[0];
840
+ // Check if it's chartRef.current or similar
841
+ if (t.isMemberExpression(firstArg) && t.isIdentifier(firstArg.property) && firstArg.property.name === 'current') {
842
+ // Try to find what element the ref is attached to
843
+ const refName = t.isIdentifier(firstArg.object) ? firstArg.object.name : null;
844
+ if (refName) {
845
+ ComponentLinter.checkRefElementType(ast, refName, rules.elementType, libraryName, violations);
846
+ }
847
+ }
848
+ }
849
+ }
850
+ },
851
+ // Check for function calls without new (if requiresNew is true)
852
+ CallExpression(path) {
853
+ if (t.isIdentifier(path.node.callee) && path.node.callee.name === rules.constructorName && rules.requiresNew === true) {
854
+ violations.push({
855
+ rule: 'library-initialization',
856
+ severity: 'critical',
857
+ line: path.node.loc?.start.line || 0,
858
+ column: path.node.loc?.start.column || 0,
859
+ message: `${libraryName}: ${rules.constructorName} requires 'new' keyword`,
860
+ code: `new ${rules.constructorName}(...)`,
861
+ });
862
+ }
863
+ },
864
+ });
865
+ return violations;
866
+ }
867
+ /**
868
+ * Check if a library is directly instantiated in the component code
869
+ * Returns false if the library is only used indirectly (e.g., by dependency components)
870
+ */
871
+ static isLibraryDirectlyInstantiated(ast, constructorName) {
872
+ let isDirectlyUsed = false;
873
+ traverse(ast, {
874
+ // Check for: new Chart(...), new ApexCharts(...), etc.
875
+ NewExpression(path) {
876
+ if (t.isIdentifier(path.node.callee) && path.node.callee.name === constructorName) {
877
+ isDirectlyUsed = true;
878
+ }
879
+ },
880
+ // Check for: Chart.register(...), Chart.defaults.set(...), etc.
881
+ CallExpression(path) {
882
+ if (t.isMemberExpression(path.node.callee) &&
883
+ t.isIdentifier(path.node.callee.object) &&
884
+ path.node.callee.object.name === constructorName) {
885
+ isDirectlyUsed = true;
886
+ }
887
+ },
888
+ });
889
+ return isDirectlyUsed;
890
+ }
891
+ /**
892
+ * Check if a ref is attached to the correct element type
893
+ */
894
+ static checkRefElementType(ast, refName, expectedType, libraryName, violations) {
895
+ traverse(ast, {
896
+ JSXElement(path) {
897
+ const openingElement = path.node.openingElement;
898
+ // Check if this element has a ref attribute
899
+ const refAttr = openingElement.attributes.find((attr) => t.isJSXAttribute(attr) && t.isJSXIdentifier(attr.name) && attr.name.name === 'ref');
900
+ if (refAttr && t.isJSXAttribute(refAttr)) {
901
+ // Check if the ref value matches our refName
902
+ const refValue = refAttr.value;
903
+ if (t.isJSXExpressionContainer(refValue) && t.isIdentifier(refValue.expression) && refValue.expression.name === refName) {
904
+ // Check element type
905
+ const elementName = t.isJSXIdentifier(openingElement.name) ? openingElement.name.name : '';
906
+ if (elementName.toLowerCase() !== expectedType.toLowerCase()) {
907
+ violations.push({
908
+ rule: 'library-element-type',
909
+ severity: 'critical',
910
+ line: openingElement.loc?.start.line || 0,
911
+ column: openingElement.loc?.start.column || 0,
912
+ message: `${libraryName} requires a <${expectedType}> element, not <${elementName}>`,
913
+ code: `<${expectedType} ref={${refName}}>`,
914
+ });
915
+ }
916
+ }
917
+ }
918
+ },
919
+ });
920
+ }
921
+ /**
922
+ * Check library lifecycle methods (render, destroy, etc.)
923
+ */
924
+ static checkLibraryLifecycle(ast, libraryName, rules) {
925
+ const violations = [];
926
+ // Track which methods are called
927
+ const calledMethods = new Set();
928
+ const instanceVariables = new Set();
929
+ traverse(ast, {
930
+ // Track instance variables
931
+ VariableDeclarator(path) {
932
+ if (t.isNewExpression(path.node.init) && t.isIdentifier(path.node.init.callee)) {
933
+ if (t.isIdentifier(path.node.id)) {
934
+ instanceVariables.add(path.node.id.name);
935
+ }
936
+ }
937
+ },
938
+ // Track method calls
939
+ CallExpression(path) {
940
+ if (t.isMemberExpression(path.node.callee)) {
941
+ const callee = path.node.callee;
942
+ if (t.isIdentifier(callee.property)) {
943
+ const methodName = callee.property.name;
944
+ const objectName = t.isIdentifier(callee.object) ? callee.object.name : null;
945
+ if (objectName && instanceVariables.has(objectName)) {
946
+ calledMethods.add(methodName);
947
+ }
948
+ }
949
+ }
950
+ },
951
+ });
952
+ // Check required methods
953
+ if (rules.requiredMethods) {
954
+ for (const method of rules.requiredMethods) {
955
+ if (!calledMethods.has(method)) {
956
+ violations.push({
957
+ rule: 'library-lifecycle',
958
+ severity: 'high',
959
+ line: 0,
960
+ column: 0,
961
+ message: `${libraryName}: Missing required method call '${method}()' after initialization`,
962
+ code: `instance.${method}()`,
963
+ });
964
+ }
965
+ }
966
+ }
967
+ // Check cleanup in useEffect
968
+ if (rules.cleanupMethods && rules.cleanupMethods.length > 0) {
969
+ // First, check if the library is directly instantiated in the component
970
+ // If it's only used by dependency components, skip cleanup check
971
+ const isLibraryDirectlyUsed = ComponentLinter.isLibraryDirectlyInstantiated(ast, rules.constructorName);
972
+ if (!isLibraryDirectlyUsed) {
973
+ // Library is only used indirectly (e.g., by dependency components)
974
+ // No cleanup check needed
975
+ return violations;
976
+ }
977
+ let hasCleanup = false;
978
+ // Track local variables that are instances of the library
979
+ const libraryInstances = new Set();
980
+ traverse(ast, {
981
+ CallExpression(path) {
982
+ // Check for both useEffect and React.useEffect
983
+ const isUseEffect = (t.isIdentifier(path.node.callee) && path.node.callee.name === 'useEffect') ||
984
+ (t.isMemberExpression(path.node.callee) &&
985
+ t.isIdentifier(path.node.callee.object) &&
986
+ path.node.callee.object.name === 'React' &&
987
+ t.isIdentifier(path.node.callee.property) &&
988
+ path.node.callee.property.name === 'useEffect');
989
+ if (isUseEffect) {
990
+ const firstArg = path.node.arguments[0];
991
+ if (t.isArrowFunctionExpression(firstArg) || t.isFunctionExpression(firstArg)) {
992
+ // First, identify local variables that are library instances
993
+ // e.g., const chart = new ApexCharts(...)
994
+ libraryInstances.clear();
995
+ traverse(firstArg, {
996
+ VariableDeclarator(varPath) {
997
+ if (t.isIdentifier(varPath.node.id) &&
998
+ t.isNewExpression(varPath.node.init) &&
999
+ t.isIdentifier(varPath.node.init.callee) &&
1000
+ varPath.node.init.callee.name === rules.constructorName) {
1001
+ libraryInstances.add(varPath.node.id.name);
1002
+ }
1003
+ },
1004
+ }, path.scope, path.state, path);
1005
+ // Check if it returns a cleanup function
1006
+ traverse(firstArg, {
1007
+ ReturnStatement(returnPath) {
1008
+ if (t.isArrowFunctionExpression(returnPath.node.argument) || t.isFunctionExpression(returnPath.node.argument)) {
1009
+ // Check if cleanup function calls destroy
1010
+ traverse(returnPath.node.argument, {
1011
+ CallExpression(cleanupPath) {
1012
+ if (t.isMemberExpression(cleanupPath.node.callee)) {
1013
+ const callee = cleanupPath.node.callee;
1014
+ // Check if the method name is a cleanup method
1015
+ if (t.isIdentifier(callee.property) && rules.cleanupMethods.includes(callee.property.name)) {
1016
+ // Pattern 1: instance.destroy() where instance is a tracked library instance
1017
+ // e.g., chart.destroy() where chart is from const chart = new ApexCharts(...)
1018
+ if (t.isIdentifier(callee.object) && libraryInstances.has(callee.object.name)) {
1019
+ hasCleanup = true;
1020
+ }
1021
+ // Pattern 2: ref.current.destroy() or ref.current._chart.destroy()
1022
+ // Any member expression chain ending in cleanup method
1023
+ // e.g., chartRef.current._chart.destroy()
1024
+ else if (t.isMemberExpression(callee.object)) {
1025
+ hasCleanup = true;
1026
+ }
1027
+ // Pattern 3: d3.select(...).selectAll('*').remove()
1028
+ // Chained method calls ending in cleanup
1029
+ else if (t.isCallExpression(callee.object)) {
1030
+ hasCleanup = true;
1031
+ }
1032
+ // Pattern 4: Any identifier calling cleanup method
1033
+ // e.g., selection.remove() where selection = d3.select(...)
1034
+ else if (t.isIdentifier(callee.object)) {
1035
+ hasCleanup = true;
1036
+ }
1037
+ }
1038
+ }
1039
+ },
1040
+ }, returnPath.scope, returnPath.state, returnPath);
1041
+ }
1042
+ },
1043
+ }, path.scope, path.state, path);
1044
+ }
1045
+ }
1046
+ },
1047
+ });
1048
+ if (!hasCleanup) {
1049
+ violations.push({
1050
+ rule: 'library-cleanup',
1051
+ severity: 'medium',
1052
+ line: 0,
1053
+ column: 0,
1054
+ message: `${libraryName}: Missing cleanup in useEffect. Call ${rules.cleanupMethods.join(' or ')} in cleanup function`,
1055
+ code: `useEffect(() => {\n // ... initialization\n return () => {\n instance.${rules.cleanupMethods[0]}();\n };\n}, []);`,
1056
+ });
1057
+ }
1058
+ }
1059
+ return violations;
1060
+ }
1061
+ /**
1062
+ * Check library options and configuration
1063
+ */
1064
+ static checkLibraryOptions(ast, libraryName, rules) {
1065
+ const violations = [];
1066
+ traverse(ast, {
1067
+ ObjectExpression(path) {
1068
+ // Check if this might be a config object for the library
1069
+ const properties = path.node.properties.filter((p) => t.isObjectProperty(p));
1070
+ const propNames = properties.filter((p) => t.isIdentifier(p.key)).map((p) => p.key.name);
1071
+ // Check for required properties
1072
+ if (rules.requiredProperties) {
1073
+ const hasChartType = propNames.some((name) => rules.requiredProperties.includes(name));
1074
+ if (hasChartType) {
1075
+ // This looks like a config object, check all required props
1076
+ for (const required of rules.requiredProperties) {
1077
+ if (!propNames.includes(required)) {
1078
+ violations.push({
1079
+ rule: 'library-options',
1080
+ severity: 'high',
1081
+ line: path.node.loc?.start.line || 0,
1082
+ column: path.node.loc?.start.column || 0,
1083
+ message: `${libraryName}: Missing required option '${required}'`,
1084
+ code: `${required}: /* value */`,
1085
+ });
1086
+ }
1087
+ }
1088
+ }
1089
+ }
1090
+ // Check property types
1091
+ if (rules.propertyTypes) {
1092
+ for (const prop of properties) {
1093
+ if (t.isObjectProperty(prop) && t.isIdentifier(prop.key)) {
1094
+ const propName = prop.key.name;
1095
+ const expectedType = rules.propertyTypes[propName];
1096
+ if (expectedType) {
1097
+ // Check if the value matches expected type
1098
+ if (expectedType.includes('array') && !t.isArrayExpression(prop.value)) {
1099
+ violations.push({
1100
+ rule: 'library-options',
1101
+ severity: 'medium',
1102
+ line: prop.loc?.start.line || 0,
1103
+ column: prop.loc?.start.column || 0,
1104
+ message: `${libraryName}: Option '${propName}' should be an array`,
1105
+ code: `${propName}: []`,
1106
+ });
1107
+ }
1108
+ }
1109
+ }
1110
+ }
1111
+ }
1112
+ },
1113
+ });
1114
+ return violations;
1115
+ }
1116
+ /**
1117
+ * Execute pre-compiled validators from cache
1118
+ */
1119
+ static executeCompiledValidators(ast, libraryName, globalVariable, validators, debugMode) {
1120
+ const violations = [];
1121
+ // Create context object for validators
1122
+ const context = {
1123
+ libraryName,
1124
+ globalVariable,
1125
+ instanceVariables: new Set(),
1126
+ violations: [], // Validators push violations here
1127
+ };
1128
+ // First pass: identify library instance variables
1129
+ traverse(ast, {
1130
+ VariableDeclarator(path) {
1131
+ if (t.isNewExpression(path.node.init) && t.isIdentifier(path.node.init.callee)) {
1132
+ // Check if it's a library constructor
1133
+ if (path.node.init.callee.name === globalVariable) {
1134
+ if (t.isIdentifier(path.node.id)) {
1135
+ context.instanceVariables.add(path.node.id.name);
1136
+ }
1137
+ }
1138
+ }
1139
+ },
1140
+ });
1141
+ // Execute each compiled validator
1142
+ for (const [validatorName, validator] of Object.entries(validators)) {
1143
+ if (validator && validator.validateFn) {
1144
+ const beforeCount = context.violations.length;
1145
+ // Log that we're running this specific validator
1146
+ if (debugMode) {
1147
+ console.log(` ├─ 🔬 Running ${libraryName} validator: ${validatorName}`);
1148
+ if (validator.description) {
1149
+ console.log(` │ ℹ️ ${validator.description}`);
1150
+ }
1151
+ }
1152
+ // Traverse AST and apply validator
1153
+ traverse(ast, {
1154
+ enter(path) {
1155
+ try {
1156
+ // Validators don't return violations, they push to context.violations
1157
+ validator.validateFn(ast, path, t, context);
1158
+ }
1159
+ catch (error) {
1160
+ // Validator execution error - log but don't crash
1161
+ console.warn(`Validator ${validatorName} failed:`, error);
1162
+ if (debugMode) {
1163
+ console.error('Full error:', error);
1164
+ }
1165
+ }
1166
+ },
1167
+ });
1168
+ // Debug logging for this specific validator
1169
+ const newViolations = context.violations.length - beforeCount;
1170
+ if (debugMode && newViolations > 0) {
1171
+ console.log(` │ ✓ Found ${newViolations} violation${newViolations > 1 ? 's' : ''}`);
1172
+ // Show the violations from this validator
1173
+ const validatorViolations = context.violations.slice(beforeCount);
1174
+ validatorViolations.forEach((v) => {
1175
+ const icon = v.type === 'error' || v.severity === 'critical'
1176
+ ? '🔴'
1177
+ : v.type === 'warning' || v.severity === 'high'
1178
+ ? '🟠'
1179
+ : v.severity === 'medium'
1180
+ ? '🟡'
1181
+ : '🟢';
1182
+ console.log(` │ ${icon} Line ${v.line || 'unknown'}: ${v.message}`);
1183
+ if (v.suggestion) {
1184
+ console.log(` │ 💡 ${v.suggestion}`);
1185
+ }
1186
+ });
1187
+ }
1188
+ else if (debugMode) {
1189
+ console.log(` │ ✓ No violations found`);
1190
+ }
1191
+ }
1192
+ }
1193
+ // Convert context violations to standard format
1194
+ const standardViolations = context.violations.map((v) => ({
1195
+ rule: `${libraryName.toLowerCase()}-validator`,
1196
+ severity: v.severity || (v.type === 'error' ? 'critical' : v.type === 'warning' ? 'high' : 'medium'),
1197
+ line: v.line || 0,
1198
+ column: v.column || 0,
1199
+ message: v.message,
1200
+ code: v.code,
1201
+ }));
1202
+ violations.push(...standardViolations);
1203
+ return violations;
1204
+ }
1205
+ }
1206
+ //# sourceMappingURL=component-linter.js.map